1use std::iter;
2
3use hir::{EditionedFileId, FilePosition, FileRange, HirFileId, InFile, Semantics};
4use ide_db::{
5 FxHashMap, FxHashSet, RootDatabase,
6 base_db::SourceDatabase,
7 defs::{Definition, IdentClass},
8 helpers::pick_best_token,
9 search::{FileReference, ReferenceCategory, SearchScope},
10 syntax_helpers::node_ext::{
11 eq_label_lt, find_loops, for_each_tail_expr, full_path_of_name_ref,
12 is_closure_or_blk_with_modif, preorder_expr_with_ctx_checker,
13 },
14};
15use syntax::{
16 AstNode,
17 SyntaxKind::{self, IDENT, INT_NUMBER},
18 SyntaxToken, T, TextRange, WalkEvent,
19 ast::{self, HasLoopBody},
20 match_ast,
21};
22
23use crate::{NavigationTarget, TryToNav, goto_definition, navigation_target::ToNav};
24
25#[derive(PartialEq, Eq, Hash)]
26pub struct HighlightedRange {
27 pub range: TextRange,
28 pub category: ReferenceCategory,
32}
33
34#[derive(Default, Clone)]
35pub struct HighlightRelatedConfig {
36 pub references: bool,
37 pub exit_points: bool,
38 pub break_points: bool,
39 pub closure_captures: bool,
40 pub yield_points: bool,
41 pub branch_exit_points: bool,
42}
43
44type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;
45
46pub(crate) fn highlight_related(
59 sema: &Semantics<'_, RootDatabase>,
60 config: HighlightRelatedConfig,
61 ide_db::FilePosition { offset, file_id }: ide_db::FilePosition,
62) -> Option<Vec<HighlightedRange>> {
63 let _p = tracing::info_span!("highlight_related").entered();
64 let file_id = sema.attach_first_edition(file_id);
65 let syntax = sema.parse(file_id).syntax().clone();
66
67 let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
68 T![?] => 4, T![->] | T![=>] => 4,
70 kind if kind.is_keyword(file_id.edition(sema.db)) => 3,
71 IDENT | INT_NUMBER => 2,
72 T![|] => 1,
73 _ => 0,
74 })?;
75 match token.kind() {
77 T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
78 highlight_exit_points(sema, token).remove(&file_id)
79 }
80 T![fn] | T![return] | T![->] if config.exit_points => {
81 highlight_exit_points(sema, token).remove(&file_id)
82 }
83 T![match] | T![=>] | T![if] if config.branch_exit_points => {
84 highlight_branch_exit_points(sema, token).remove(&file_id)
85 }
86 T![await] | T![async] if config.yield_points => {
87 highlight_yield_points(sema, token).remove(&file_id)
88 }
89 T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
90 highlight_break_points(sema, token).remove(&file_id)
91 }
92 T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
93 highlight_break_points(sema, token).remove(&file_id)
94 }
95 T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {
96 highlight_unsafe_points(sema, token).remove(&file_id)
97 }
98 T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
99 T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
100 _ if config.references => {
101 highlight_references(sema, token, FilePosition { file_id, offset })
102 }
103 _ => None,
104 }
105}
106
107fn highlight_closure_captures(
108 sema: &Semantics<'_, RootDatabase>,
109 token: SyntaxToken,
110 file_id: EditionedFileId,
111) -> Option<Vec<HighlightedRange>> {
112 let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
113 let search_range = closure.body()?.syntax().text_range();
114 let ty = &sema.type_of_expr(&closure.into())?.original;
115 let c = ty.as_closure()?;
116 Some(
117 c.captured_items(sema.db)
118 .into_iter()
119 .map(|capture| capture.local())
120 .flat_map(|local| {
121 let usages = Definition::Local(local)
122 .usages(sema)
123 .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))
124 .include_self_refs()
125 .all()
126 .references
127 .remove(&file_id)
128 .into_iter()
129 .flatten()
130 .map(|FileReference { category, range, .. }| HighlightedRange {
131 range,
132 category,
133 });
134 let category = if local.is_mut(sema.db) {
135 ReferenceCategory::WRITE
136 } else {
137 ReferenceCategory::empty()
138 };
139 local
140 .sources(sema.db)
141 .into_iter()
142 .flat_map(|x| x.to_nav(sema.db))
143 .filter(|decl| decl.file_id == file_id.file_id(sema.db))
144 .filter_map(|decl| decl.focus_range)
145 .map(move |range| HighlightedRange { range, category })
146 .chain(usages)
147 })
148 .collect(),
149 )
150}
151
152fn highlight_references(
153 sema: &Semantics<'_, RootDatabase>,
154 token: SyntaxToken,
155 FilePosition { file_id, offset }: FilePosition,
156) -> Option<Vec<HighlightedRange>> {
157 let defs = if let Some((range, _, _, resolution)) =
158 sema.check_for_format_args_template(token.clone(), offset)
159 {
160 match resolution.map(Definition::from) {
161 Some(def) => iter::once(def).collect(),
162 None => {
163 return Some(vec![HighlightedRange {
164 range,
165 category: ReferenceCategory::empty(),
166 }]);
167 }
168 }
169 } else {
170 find_defs(sema, token.clone())
171 };
172 let usages = defs
173 .iter()
174 .filter_map(|&d| {
175 d.usages(sema)
176 .in_scope(&SearchScope::single_file(file_id))
177 .include_self_refs()
178 .all()
179 .references
180 .remove(&file_id)
181 })
182 .flatten()
183 .map(|FileReference { category, range, .. }| HighlightedRange { range, category });
184 let mut res = FxHashSet::default();
185 for &def in &defs {
186 if let Definition::Trait(t) = def {
188 let trait_item_use_scope = (|| {
189 let name_ref = token.parent().and_then(ast::NameRef::cast)?;
190 let path = full_path_of_name_ref(&name_ref)?;
191 let parent = path.syntax().parent()?;
192 match_ast! {
193 match parent {
194 ast::UseTree(it) => it.syntax().ancestors().find(|it| {
195 ast::SourceFile::can_cast(it.kind()) || ast::Module::can_cast(it.kind())
196 }).zip(Some(true)),
197 ast::PathType(it) => it
198 .syntax()
199 .ancestors()
200 .nth(2)
201 .and_then(ast::TypeBoundList::cast)?
202 .syntax()
203 .parent()
204 .filter(|it| ast::WhereClause::can_cast(it.kind()) || ast::TypeParam::can_cast(it.kind()))?
205 .ancestors()
206 .find(|it| {
207 ast::Item::can_cast(it.kind())
208 }).zip(Some(false)),
209 _ => None,
210 }
211 }
212 })();
213 if let Some((trait_item_use_scope, use_tree)) = trait_item_use_scope {
214 res.extend(
215 if use_tree { t.items(sema.db) } else { t.items_with_supertraits(sema.db) }
216 .into_iter()
217 .filter_map(|item| {
218 Definition::from(item)
219 .usages(sema)
220 .set_scope(Some(&SearchScope::file_range(FileRange {
221 file_id,
222 range: trait_item_use_scope.text_range(),
223 })))
224 .include_self_refs()
225 .all()
226 .references
227 .remove(&file_id)
228 })
229 .flatten()
230 .map(|FileReference { category, range, .. }| HighlightedRange {
231 range,
232 category,
233 }),
234 );
235 }
236 }
237
238 if matches!(def, Definition::Label(_)) {
240 let label = token.parent_ancestors().nth(1).and_then(ast::Label::cast);
241 if let Some(block) =
242 label.and_then(|label| label.syntax().parent()).and_then(ast::BlockExpr::cast)
243 {
244 for_each_tail_expr(&block.into(), &mut |tail| {
245 if !matches!(tail, ast::Expr::BreakExpr(_)) {
246 res.insert(HighlightedRange {
247 range: tail.syntax().text_range(),
248 category: ReferenceCategory::empty(),
249 });
250 }
251 });
252 }
253 }
254
255 match def {
257 Definition::Local(local) => {
258 let category = if local.is_mut(sema.db) {
259 ReferenceCategory::WRITE
260 } else {
261 ReferenceCategory::empty()
262 };
263 local
264 .sources(sema.db)
265 .into_iter()
266 .flat_map(|x| x.to_nav(sema.db))
267 .filter(|decl| decl.file_id == file_id.file_id(sema.db))
268 .filter_map(|decl| decl.focus_range)
269 .map(|range| HighlightedRange { range, category })
270 .for_each(|x| {
271 res.insert(x);
272 });
273 }
274 def => {
275 let navs = match def {
276 Definition::Module(module) => {
277 NavigationTarget::from_module_to_decl(sema.db, module)
278 }
279 def => match def.try_to_nav(sema) {
280 Some(it) => it,
281 None => continue,
282 },
283 };
284 for nav in navs {
285 if nav.file_id != file_id.file_id(sema.db) {
286 continue;
287 }
288 let hl_range = nav.focus_range.map(|range| {
289 let category = if matches!(def, Definition::Local(l) if l.is_mut(sema.db)) {
290 ReferenceCategory::WRITE
291 } else {
292 ReferenceCategory::empty()
293 };
294 HighlightedRange { range, category }
295 });
296 if let Some(hl_range) = hl_range {
297 res.insert(hl_range);
298 }
299 }
300 }
301 }
302 }
303
304 res.extend(usages);
305 if res.is_empty() { None } else { Some(res.into_iter().collect()) }
306}
307
308pub(crate) fn highlight_branch_exit_points(
309 sema: &Semantics<'_, RootDatabase>,
310 token: SyntaxToken,
311) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
312 let mut highlights: HighlightMap = FxHashMap::default();
313
314 let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {
315 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
316 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
317 highlights.entry(file_id).or_default().insert(hrange);
318 }
319 };
320
321 let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {
322 let Some(tail) = tail else {
323 return;
324 };
325
326 for_each_tail_expr(&tail, &mut |tail| {
327 let file_id = sema.hir_file_for(tail.syntax());
328 let range = tail.syntax().text_range();
329 push_to_highlights(file_id, Some(range), highlights);
330 });
331 };
332
333 let nodes = goto_definition::find_branch_root(sema, &token).into_iter();
334 match token.kind() {
335 T![match] => {
336 for match_expr in nodes.filter_map(ast::MatchExpr::cast) {
337 let file_id = sema.hir_file_for(match_expr.syntax());
338 let range = match_expr.match_token().map(|token| token.text_range());
339 push_to_highlights(file_id, range, &mut highlights);
340
341 let Some(arm_list) = match_expr.match_arm_list() else {
342 continue;
343 };
344 for arm in arm_list.arms() {
345 push_tail_expr(arm.expr(), &mut highlights);
346 }
347 }
348 }
349 T![=>] => {
350 for arm in nodes.filter_map(ast::MatchArm::cast) {
351 let file_id = sema.hir_file_for(arm.syntax());
352 let range = arm.fat_arrow_token().map(|token| token.text_range());
353 push_to_highlights(file_id, range, &mut highlights);
354
355 push_tail_expr(arm.expr(), &mut highlights);
356 }
357 }
358 T![if] => {
359 for mut if_to_process in nodes.map(ast::IfExpr::cast) {
360 while let Some(cur_if) = if_to_process.take() {
361 let file_id = sema.hir_file_for(cur_if.syntax());
362
363 let if_kw_range = cur_if.if_token().map(|token| token.text_range());
364 push_to_highlights(file_id, if_kw_range, &mut highlights);
365
366 if let Some(then_block) = cur_if.then_branch() {
367 push_tail_expr(Some(then_block.into()), &mut highlights);
368 }
369
370 match cur_if.else_branch() {
371 Some(ast::ElseBranch::Block(else_block)) => {
372 push_tail_expr(Some(else_block.into()), &mut highlights);
373 if_to_process = None;
374 }
375 Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),
376 None => if_to_process = None,
377 }
378 }
379 }
380 }
381 _ => {}
382 }
383
384 highlights
385 .into_iter()
386 .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))
387 .collect()
388}
389
390fn hl_exit_points(
391 sema: &Semantics<'_, RootDatabase>,
392 def_token: Option<SyntaxToken>,
393 body: ast::Expr,
394) -> Option<HighlightMap> {
395 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
396
397 let mut push_to_highlights = |file_id, range| {
398 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
399 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
400 highlights.entry(file_id).or_default().insert(hrange);
401 }
402 };
403
404 if let Some(tok) = def_token {
405 let file_id = sema.hir_file_for(&tok.parent()?);
406 let range = Some(tok.text_range());
407 push_to_highlights(file_id, range);
408 }
409
410 WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {
411 let file_id = sema.hir_file_for(expr.syntax());
412
413 let range = match &expr {
414 ast::Expr::TryExpr(try_) => try_.question_mark_token().map(|token| token.text_range()),
415 ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_)
416 if sema.type_of_expr(&expr).is_some_and(|ty| ty.original.is_never()) =>
417 {
418 Some(expr.syntax().text_range())
419 }
420 _ => None,
421 };
422
423 push_to_highlights(file_id, range);
424 });
425
426 WalkExpandedExprCtx::new(sema)
429 .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)
430 .walk(&body, &mut |_, expr| {
431 let file_id = sema.hir_file_for(expr.syntax());
432
433 let range = match &expr {
434 ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()),
435 _ => None,
436 };
437
438 push_to_highlights(file_id, range);
439 });
440
441 let tail = match body {
442 ast::Expr::BlockExpr(b) => b.tail_expr(),
443 e => Some(e),
444 };
445
446 if let Some(tail) = tail {
447 for_each_tail_expr(&tail, &mut |tail| {
448 let file_id = sema.hir_file_for(tail.syntax());
449 let range = match tail {
450 ast::Expr::BreakExpr(b) => b
451 .break_token()
452 .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
453 _ => tail.syntax().text_range(),
454 };
455 push_to_highlights(file_id, Some(range));
456 });
457 }
458 Some(highlights)
459}
460
461pub(crate) fn highlight_exit_points(
463 sema: &Semantics<'_, RootDatabase>,
464 token: SyntaxToken,
465) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
466 let mut res = FxHashMap::default();
467 for def in goto_definition::find_fn_or_blocks(sema, &token) {
468 let new_map = match_ast! {
469 match def {
470 ast::Fn(fn_) => fn_.body().and_then(|body| hl_exit_points(sema, fn_.fn_token(), body.into())),
471 ast::ClosureExpr(closure) => {
472 let pipe_tok = closure.param_list().and_then(|p| p.pipe_token());
473 closure.body().and_then(|body| hl_exit_points(sema, pipe_tok, body))
474 },
475 ast::BlockExpr(blk) => match blk.modifier() {
476 Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()),
477 Some(ast::BlockModifier::Try { try_token: t, .. }) if token.kind() != T![return] => {
478 hl_exit_points(sema, Some(t), blk.into())
479 },
480 _ => continue,
481 },
482 _ => continue,
483 }
484 };
485 merge_map(&mut res, new_map);
486 }
487
488 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
489}
490
491pub(crate) fn highlight_break_points(
492 sema: &Semantics<'_, RootDatabase>,
493 token: SyntaxToken,
494) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
495 pub(crate) fn hl(
496 sema: &Semantics<'_, RootDatabase>,
497 cursor_token_kind: SyntaxKind,
498 loop_token: Option<SyntaxToken>,
499 label: Option<ast::Label>,
500 expr: ast::Expr,
501 ) -> Option<HighlightMap> {
502 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
503
504 let mut push_to_highlights = |file_id, range| {
505 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
506 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
507 highlights.entry(file_id).or_default().insert(hrange);
508 }
509 };
510
511 let label_lt = label.as_ref().and_then(|it| it.lifetime());
512
513 if let Some(range) = cover_range(
514 loop_token.as_ref().map(|tok| tok.text_range()),
515 label.as_ref().map(|it| it.syntax().text_range()),
516 ) {
517 let file_id = loop_token
518 .and_then(|tok| Some(sema.hir_file_for(&tok.parent()?)))
519 .unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax()));
520 push_to_highlights(file_id, Some(range));
521 }
522
523 WalkExpandedExprCtx::new(sema)
524 .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)
525 .walk(&expr, &mut |depth, expr| {
526 let file_id = sema.hir_file_for(expr.syntax());
527
528 let (token, token_lt) = match expr {
530 ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => {
531 (b.break_token(), b.lifetime())
532 }
533 ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => {
534 (c.continue_token(), c.lifetime())
535 }
536 _ => return,
537 };
538
539 if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) {
540 return;
541 }
542
543 let text_range = cover_range(
544 token.map(|it| it.text_range()),
545 token_lt.map(|it| it.syntax().text_range()),
546 );
547
548 push_to_highlights(file_id, text_range);
549 });
550
551 if matches!(expr, ast::Expr::BlockExpr(_)) {
552 for_each_tail_expr(&expr, &mut |tail| {
553 if matches!(tail, ast::Expr::BreakExpr(_)) {
554 return;
555 }
556
557 let file_id = sema.hir_file_for(tail.syntax());
558 let range = tail.syntax().text_range();
559 push_to_highlights(file_id, Some(range));
560 });
561 }
562
563 Some(highlights)
564 }
565
566 let Some(loops) = find_loops(sema, &token) else {
567 return FxHashMap::default();
568 };
569
570 let mut res = FxHashMap::default();
571 let token_kind = token.kind();
572 for expr in loops {
573 let new_map = match &expr {
574 ast::Expr::LoopExpr(l) => hl(sema, token_kind, l.loop_token(), l.label(), expr),
575 ast::Expr::ForExpr(f) => hl(sema, token_kind, f.for_token(), f.label(), expr),
576 ast::Expr::WhileExpr(w) => hl(sema, token_kind, w.while_token(), w.label(), expr),
577 ast::Expr::BlockExpr(e) => hl(sema, token_kind, None, e.label(), expr),
578 _ => continue,
579 };
580 merge_map(&mut res, new_map);
581 }
582
583 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
584}
585
586pub(crate) fn highlight_yield_points(
587 sema: &Semantics<'_, RootDatabase>,
588 token: SyntaxToken,
589) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
590 fn hl(
591 sema: &Semantics<'_, RootDatabase>,
592 async_token: Option<SyntaxToken>,
593 body: Option<ast::Expr>,
594 ) -> Option<HighlightMap> {
595 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
596
597 let mut push_to_highlights = |file_id, range| {
598 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
599 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
600 highlights.entry(file_id).or_default().insert(hrange);
601 }
602 };
603
604 let async_token = async_token?;
605 let async_tok_file_id = sema.hir_file_for(&async_token.parent()?);
606 push_to_highlights(async_tok_file_id, Some(async_token.text_range()));
607
608 let Some(body) = body else {
609 return Some(highlights);
610 };
611
612 WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {
613 let file_id = sema.hir_file_for(expr.syntax());
614
615 let text_range = match expr {
616 ast::Expr::AwaitExpr(expr) => expr.await_token(),
617 ast::Expr::ReturnExpr(expr) => expr.return_token(),
618 _ => None,
619 }
620 .map(|it| it.text_range());
621
622 push_to_highlights(file_id, text_range);
623 });
624
625 Some(highlights)
626 }
627
628 let mut res = FxHashMap::default();
629 for anc in goto_definition::find_fn_or_blocks(sema, &token) {
630 let new_map = match_ast! {
631 match anc {
632 ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
633 ast::BlockExpr(block_expr) => {
634 let Some(async_token) = block_expr.async_token() else {
635 continue;
636 };
637
638 if async_token == token {
642 let exit_points = hl_exit_points(
643 sema,
644 Some(async_token.clone()),
645 block_expr.clone().into(),
646 );
647 merge_map(&mut res, exit_points);
648 }
649
650 hl(sema, Some(async_token), Some(block_expr.into()))
651 },
652 ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()),
653 _ => continue,
654 }
655 };
656 merge_map(&mut res, new_map);
657 }
658
659 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()
660}
661
662fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
663 match (r0, r1) {
664 (Some(r0), Some(r1)) => Some(r0.cover(r1)),
665 (Some(range), None) => Some(range),
666 (None, Some(range)) => Some(range),
667 (None, None) => None,
668 }
669}
670
671fn find_defs<'db>(
672 sema: &Semantics<'db, RootDatabase>,
673 token: SyntaxToken,
674) -> FxHashSet<Definition<'db>> {
675 sema.descend_into_macros_exact(token)
676 .into_iter()
677 .filter_map(|token| IdentClass::classify_token(sema, &token))
678 .flat_map(IdentClass::definitions_no_ops)
679 .collect()
680}
681
682fn original_frange(
683 db: &dyn SourceDatabase,
684 file_id: HirFileId,
685 text_range: Option<TextRange>,
686) -> Option<FileRange> {
687 InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)
688}
689
690fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {
691 let Some(new) = new else {
692 return;
693 };
694 new.into_iter().for_each(|(file_id, ranges)| {
695 res.entry(file_id).or_default().extend(ranges);
696 });
697}
698
699struct WalkExpandedExprCtx<'a, 'db> {
703 sema: &'a Semantics<'db, RootDatabase>,
704 depth: usize,
705 check_ctx: &'static dyn Fn(&ast::Expr) -> bool,
706}
707
708impl<'a, 'db> WalkExpandedExprCtx<'a, 'db> {
709 fn new(sema: &'a Semantics<'db, RootDatabase>) -> Self {
710 Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif }
711 }
712
713 fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self {
714 Self { check_ctx, ..*self }
715 }
716
717 fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) {
718 preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent<ast::Expr>| {
719 match ev {
720 syntax::WalkEvent::Enter(expr) => {
721 cb(self.depth, expr.clone());
722
723 if Self::should_change_depth(&expr) {
724 self.depth += 1;
725 }
726
727 if let ast::Expr::MacroExpr(expr) = expr
728 && let Some(expanded) =
729 expr.macro_call().and_then(|call| self.sema.expand_macro_call(&call))
730 {
731 match_ast! {
732 match (expanded.value) {
733 ast::MacroStmts(it) => {
734 self.handle_expanded(it, cb);
735 },
736 ast::Expr(it) => {
737 self.walk(&it, cb);
738 },
739 _ => {}
740 }
741 }
742 }
743 }
744 syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => {
745 self.depth -= 1;
746 }
747 _ => {}
748 }
749 false
750 })
751 }
752
753 fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) {
754 if let Some(expr) = expanded.expr() {
755 self.walk(&expr, cb);
756 }
757
758 for stmt in expanded.statements() {
759 if let ast::Stmt::ExprStmt(stmt) = stmt
760 && let Some(expr) = stmt.expr()
761 {
762 self.walk(&expr, cb);
763 }
764 }
765 }
766
767 fn should_change_depth(expr: &ast::Expr) -> bool {
768 match expr {
769 ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true,
770 ast::Expr::BlockExpr(blk) if blk.label().is_some() => true,
771 _ => false,
772 }
773 }
774
775 fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool {
776 match expr {
777 ast::Expr::BlockExpr(b) => matches!(
778 b.modifier(),
779 Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_))
780 ),
781 ast::Expr::ClosureExpr(_) => true,
782 _ => false,
783 }
784 }
785}
786
787pub(crate) fn highlight_unsafe_points(
788 sema: &Semantics<'_, RootDatabase>,
789 token: SyntaxToken,
790) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
791 fn hl(
792 sema: &Semantics<'_, RootDatabase>,
793 unsafe_token: &SyntaxToken,
794 block_expr: Option<ast::BlockExpr>,
795 ) -> Option<FxHashMap<EditionedFileId, Vec<HighlightedRange>>> {
796 let mut highlights: FxHashMap<EditionedFileId, Vec<_>> = FxHashMap::default();
797
798 let mut push_to_highlights = |file_id, range| {
799 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
800 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
801 highlights.entry(file_id).or_default().push(hrange);
802 }
803 };
804
805 let unsafe_token_file_id = sema.hir_file_for(&unsafe_token.parent()?);
807 push_to_highlights(unsafe_token_file_id, Some(unsafe_token.text_range()));
808
809 if let Some(block) = block_expr {
811 let unsafe_ops = sema.get_unsafe_ops_for_unsafe_block(block);
812 for unsafe_op in unsafe_ops {
813 push_to_highlights(unsafe_op.file_id, Some(unsafe_op.value.text_range()));
814 }
815 }
816
817 Some(highlights)
818 }
819
820 hl(sema, &token, token.parent().and_then(ast::BlockExpr::cast)).unwrap_or_default()
821}
822
823#[cfg(test)]
824mod tests {
825 use itertools::Itertools;
826
827 use crate::fixture;
828
829 use super::*;
830
831 const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
832 break_points: true,
833 exit_points: true,
834 references: true,
835 closure_captures: true,
836 yield_points: true,
837 branch_exit_points: true,
838 };
839
840 #[track_caller]
841 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
842 check_with_config(ra_fixture, ENABLED_CONFIG);
843 }
844
845 #[track_caller]
846 fn check_with_config(
847 #[rust_analyzer::rust_fixture] ra_fixture: &str,
848 config: HighlightRelatedConfig,
849 ) {
850 let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
851
852 let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
853
854 let mut expected =
855 annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();
856
857 let mut actual: Vec<(TextRange, String)> = hls
858 .into_iter()
859 .map(|hl| {
860 (
861 hl.range,
862 hl.category.iter_names().map(|(name, _flag)| name.to_lowercase()).join(","),
863 )
864 })
865 .collect();
866 actual.sort_by_key(|(range, _)| range.start());
867 expected.sort_by_key(|(range, _)| range.start());
868
869 assert_eq!(expected, actual);
870 }
871
872 #[test]
873 fn test_hl_unsafe_block() {
874 check(
875 r#"
876fn foo() {
877 unsafe fn this_is_unsafe_function() {}
878
879 unsa$0fe {
880 //^^^^^^
881 let raw_ptr = &42 as *const i32;
882 let val = *raw_ptr;
883 //^^^^^^^^
884
885 let mut_ptr = &mut 5 as *mut i32;
886 *mut_ptr = 10;
887 //^^^^^^^^
888
889 this_is_unsafe_function();
890 //^^^^^^^^^^^^^^^^^^^^^^^^^
891 }
892
893}
894"#,
895 );
896 }
897
898 #[test]
899 fn test_hl_tuple_fields() {
900 check(
901 r#"
902struct Tuple(u32, u32);
903
904fn foo(t: Tuple) {
905 t.0$0;
906 // ^ read
907 t.0;
908 // ^ read
909}
910"#,
911 );
912 }
913
914 #[test]
915 fn test_hl_module() {
916 check(
917 r#"
918//- /lib.rs
919mod foo$0;
920 // ^^^
921//- /foo.rs
922struct Foo;
923"#,
924 );
925 }
926
927 #[test]
928 fn test_hl_self_in_crate_root() {
929 check(
930 r#"
931use crate$0;
932 //^^^^^ import
933use self;
934 //^^^^ import
935mod __ {
936 use super;
937 //^^^^^ import
938}
939"#,
940 );
941 check(
942 r#"
943//- /main.rs crate:main deps:lib
944use lib$0;
945 //^^^ import
946//- /lib.rs crate:lib
947"#,
948 );
949 }
950
951 #[test]
952 fn test_hl_self_in_module() {
953 check(
954 r#"
955//- /lib.rs
956mod foo;
957//- /foo.rs
958use self$0;
959 // ^^^^ import
960"#,
961 );
962 }
963
964 #[test]
965 fn test_hl_local() {
966 check(
967 r#"
968fn foo() {
969 let mut bar = 3;
970 // ^^^ write
971 bar$0;
972 // ^^^ read
973}
974"#,
975 );
976 }
977
978 #[test]
979 fn test_hl_local_in_attr() {
980 check(
981 r#"
982//- proc_macros: identity
983#[proc_macros::identity]
984fn foo() {
985 let mut bar = 3;
986 // ^^^ write
987 bar$0;
988 // ^^^ read
989}
990"#,
991 );
992 }
993
994 #[test]
995 fn test_multi_macro_usage() {
996 check(
997 r#"
998macro_rules! foo {
999 ($ident:ident) => {
1000 fn $ident() -> $ident { loop {} }
1001 struct $ident;
1002 }
1003}
1004
1005foo!(bar$0);
1006 // ^^^
1007fn foo() {
1008 let bar: bar = bar();
1009 // ^^^
1010 // ^^^
1011}
1012"#,
1013 );
1014 check(
1015 r#"
1016macro_rules! foo {
1017 ($ident:ident) => {
1018 fn $ident() -> $ident { loop {} }
1019 struct $ident;
1020 }
1021}
1022
1023foo!(bar);
1024 // ^^^
1025fn foo() {
1026 let bar: bar$0 = bar();
1027 // ^^^
1028}
1029"#,
1030 );
1031 }
1032
1033 #[test]
1034 fn test_hl_yield_points() {
1035 check(
1036 r#"
1037pub async fn foo() {
1038 // ^^^^^
1039 let x = foo()
1040 .await$0
1041 // ^^^^^
1042 .await;
1043 // ^^^^^
1044 || { 0.await };
1045 (async { 0.await }).await
1046 // ^^^^^
1047}
1048"#,
1049 );
1050 }
1051
1052 #[test]
1053 fn test_hl_yield_points2() {
1054 check(
1055 r#"
1056pub async$0 fn foo() {
1057 // ^^^^^
1058 let x = foo()
1059 .await
1060 // ^^^^^
1061 .await;
1062 // ^^^^^
1063 || { 0.await };
1064 (async { 0.await }).await
1065 // ^^^^^
1066}
1067"#,
1068 );
1069 }
1070
1071 #[test]
1072 fn test_hl_exit_points_of_async_blocks() {
1073 check(
1074 r#"
1075pub fn foo() {
1076 let x = async$0 {
1077 // ^^^^^
1078 0.await;
1079 // ^^^^^
1080 0?;
1081 // ^
1082 return 0;
1083 // ^^^^^^
1084 0
1085 // ^
1086 };
1087}
1088"#,
1089 );
1090 }
1091
1092 #[test]
1093 fn test_hl_let_else_yield_points() {
1094 check(
1095 r#"
1096pub async fn foo() {
1097 // ^^^^^
1098 let x = foo()
1099 .await$0
1100 // ^^^^^
1101 .await;
1102 // ^^^^^
1103 || { 0.await };
1104 let Some(_) = None else {
1105 foo().await
1106 // ^^^^^
1107 };
1108 (async { 0.await }).await
1109 // ^^^^^
1110}
1111"#,
1112 );
1113 }
1114
1115 #[test]
1116 fn test_hl_yield_nested_fn() {
1117 check(
1118 r#"
1119async fn foo() {
1120 async fn foo2() {
1121 // ^^^^^
1122 async fn foo3() {
1123 0.await
1124 }
1125 0.await$0
1126 // ^^^^^
1127 }
1128 0.await
1129}
1130"#,
1131 );
1132 }
1133
1134 #[test]
1135 fn test_hl_yield_nested_async_blocks() {
1136 check(
1137 r#"
1138async fn foo() {
1139 (async {
1140 // ^^^^^
1141 (async { 0.await }).await$0
1142 // ^^^^^
1143 }).await;
1144}
1145"#,
1146 );
1147 }
1148
1149 #[test]
1150 fn test_hl_exit_points() {
1151 check(
1152 r#"
1153 fn foo() -> u32 {
1154//^^
1155 if true {
1156 return$0 0;
1157 // ^^^^^^
1158 }
1159
1160 0?;
1161 // ^
1162 0xDEAD_BEEF
1163 // ^^^^^^^^^^^
1164}
1165"#,
1166 );
1167 }
1168
1169 #[test]
1170 fn test_hl_exit_points2() {
1171 check(
1172 r#"
1173 fn foo() ->$0 u32 {
1174//^^
1175 if true {
1176 return 0;
1177 // ^^^^^^
1178 }
1179
1180 0?;
1181 // ^
1182 0xDEAD_BEEF
1183 // ^^^^^^^^^^^
1184}
1185"#,
1186 );
1187 }
1188
1189 #[test]
1190 fn test_hl_exit_points3() {
1191 check(
1192 r#"
1193 fn$0 foo() -> u32 {
1194//^^
1195 if true {
1196 return 0;
1197 // ^^^^^^
1198 }
1199
1200 0?;
1201 // ^
1202 0xDEAD_BEEF
1203 // ^^^^^^^^^^^
1204}
1205"#,
1206 );
1207 }
1208
1209 #[test]
1210 fn test_hl_let_else_exit_points() {
1211 check(
1212 r#"
1213 fn$0 foo() -> u32 {
1214//^^
1215 let Some(bar) = None else {
1216 return 0;
1217 // ^^^^^^
1218 };
1219
1220 0?;
1221 // ^
1222 0xDEAD_BEEF
1223 // ^^^^^^^^^^^
1224}
1225"#,
1226 );
1227 }
1228
1229 #[test]
1230 fn test_hl_prefer_ref_over_tail_exit() {
1231 check(
1232 r#"
1233fn foo() -> u32 {
1234// ^^^
1235 if true {
1236 return 0;
1237 }
1238
1239 0?;
1240
1241 foo$0()
1242 // ^^^
1243}
1244"#,
1245 );
1246 }
1247
1248 #[test]
1249 fn test_hl_never_call_is_exit_point() {
1250 check(
1251 r#"
1252struct Never;
1253impl Never {
1254 fn never(self) -> ! { loop {} }
1255}
1256macro_rules! never {
1257 () => { never() }
1258 // ^^^^^^^
1259}
1260fn never() -> ! { loop {} }
1261 fn foo() ->$0 u32 {
1262//^^
1263 never();
1264 // ^^^^^^^
1265 never!();
1266 // ^^^^^^^^
1267
1268 Never.never();
1269 // ^^^^^^^^^^^^^
1270
1271 0
1272 // ^
1273}
1274"#,
1275 );
1276 }
1277
1278 #[test]
1279 fn test_hl_inner_tail_exit_points() {
1280 check(
1281 r#"
1282 fn foo() ->$0 u32 {
1283//^^
1284 if true {
1285 unsafe {
1286 return 5;
1287 // ^^^^^^
1288 5
1289 // ^
1290 }
1291 } else if false {
1292 0
1293 // ^
1294 } else {
1295 match 5 {
1296 6 => 100,
1297 // ^^^
1298 7 => loop {
1299 break 5;
1300 // ^^^^^
1301 }
1302 8 => 'a: loop {
1303 'b: loop {
1304 break 'a 5;
1305 // ^^^^^
1306 break 'b 5;
1307 break 5;
1308 };
1309 }
1310 //
1311 _ => 500,
1312 // ^^^
1313 }
1314 }
1315}
1316"#,
1317 );
1318 }
1319
1320 #[test]
1321 fn test_hl_inner_tail_exit_points_labeled_block() {
1322 check(
1323 r#"
1324 fn foo() ->$0 u32 {
1325//^^
1326 'foo: {
1327 break 'foo 0;
1328 // ^^^^^
1329 loop {
1330 break;
1331 break 'foo 0;
1332 // ^^^^^
1333 }
1334 0
1335 // ^
1336 }
1337}
1338"#,
1339 );
1340 }
1341
1342 #[test]
1343 fn test_hl_inner_tail_exit_points_loops() {
1344 check(
1345 r#"
1346 fn foo() ->$0 u32 {
1347//^^
1348 'foo: while { return 0; true } {
1349 // ^^^^^^
1350 break 'foo 0;
1351 // ^^^^^
1352 return 0;
1353 // ^^^^^^
1354 }
1355}
1356"#,
1357 );
1358 }
1359
1360 #[test]
1361 fn test_hl_break_loop() {
1362 check(
1363 r#"
1364fn foo() {
1365 'outer: loop {
1366 // ^^^^^^^^^^^^
1367 break;
1368 // ^^^^^
1369 'inner: loop {
1370 break;
1371 'innermost: loop {
1372 break 'outer;
1373 // ^^^^^^^^^^^^
1374 break 'inner;
1375 }
1376 break$0 'outer;
1377 // ^^^^^^^^^^^^
1378 break;
1379 }
1380 break;
1381 // ^^^^^
1382 }
1383}
1384"#,
1385 );
1386 }
1387
1388 #[test]
1389 fn test_hl_break_loop2() {
1390 check(
1391 r#"
1392fn foo() {
1393 'outer: loop {
1394 break;
1395 'inner: loop {
1396 // ^^^^^^^^^^^^
1397 break;
1398 // ^^^^^
1399 'innermost: loop {
1400 break 'outer;
1401 break 'inner;
1402 // ^^^^^^^^^^^^
1403 }
1404 break 'outer;
1405 break$0;
1406 // ^^^^^
1407 }
1408 break;
1409 }
1410}
1411"#,
1412 );
1413 }
1414
1415 #[test]
1416 fn test_hl_break_for() {
1417 check(
1418 r#"
1419fn foo() {
1420 'outer: for _ in () {
1421 // ^^^^^^^^^^^
1422 break;
1423 // ^^^^^
1424 'inner: for _ in () {
1425 break;
1426 'innermost: for _ in () {
1427 break 'outer;
1428 // ^^^^^^^^^^^^
1429 break 'inner;
1430 }
1431 break$0 'outer;
1432 // ^^^^^^^^^^^^
1433 break;
1434 }
1435 break;
1436 // ^^^^^
1437 }
1438}
1439"#,
1440 );
1441 }
1442
1443 #[test]
1444 fn test_hl_break_for_but_not_continue() {
1445 check(
1446 r#"
1447fn foo() {
1448 'outer: for _ in () {
1449 // ^^^^^^^^^^^
1450 break;
1451 // ^^^^^
1452 continue;
1453 'inner: for _ in () {
1454 break;
1455 continue;
1456 'innermost: for _ in () {
1457 continue 'outer;
1458 break 'outer;
1459 // ^^^^^^^^^^^^
1460 continue 'inner;
1461 break 'inner;
1462 }
1463 break$0 'outer;
1464 // ^^^^^^^^^^^^
1465 continue 'outer;
1466 break;
1467 continue;
1468 }
1469 break;
1470 // ^^^^^
1471 continue;
1472 }
1473}
1474"#,
1475 );
1476 }
1477
1478 #[test]
1479 fn test_hl_continue_for_but_not_break() {
1480 check(
1481 r#"
1482fn foo() {
1483 'outer: for _ in () {
1484 // ^^^^^^^^^^^
1485 break;
1486 continue;
1487 // ^^^^^^^^
1488 'inner: for _ in () {
1489 break;
1490 continue;
1491 'innermost: for _ in () {
1492 continue 'outer;
1493 // ^^^^^^^^^^^^^^^
1494 break 'outer;
1495 continue 'inner;
1496 break 'inner;
1497 }
1498 break 'outer;
1499 continue$0 'outer;
1500 // ^^^^^^^^^^^^^^^
1501 break;
1502 continue;
1503 }
1504 break;
1505 continue;
1506 // ^^^^^^^^
1507 }
1508}
1509"#,
1510 );
1511 }
1512
1513 #[test]
1514 fn test_hl_break_and_continue() {
1515 check(
1516 r#"
1517fn foo() {
1518 'outer: fo$0r _ in () {
1519 // ^^^^^^^^^^^
1520 break;
1521 // ^^^^^
1522 continue;
1523 // ^^^^^^^^
1524 'inner: for _ in () {
1525 break;
1526 continue;
1527 'innermost: for _ in () {
1528 continue 'outer;
1529 // ^^^^^^^^^^^^^^^
1530 break 'outer;
1531 // ^^^^^^^^^^^^
1532 continue 'inner;
1533 break 'inner;
1534 }
1535 break 'outer;
1536 // ^^^^^^^^^^^^
1537 continue 'outer;
1538 // ^^^^^^^^^^^^^^^
1539 break;
1540 continue;
1541 }
1542 break;
1543 // ^^^^^
1544 continue;
1545 // ^^^^^^^^
1546 }
1547}
1548"#,
1549 );
1550 }
1551
1552 #[test]
1553 fn test_hl_break_while() {
1554 check(
1555 r#"
1556fn foo() {
1557 'outer: while true {
1558 // ^^^^^^^^^^^^^
1559 break;
1560 // ^^^^^
1561 'inner: while true {
1562 break;
1563 'innermost: while true {
1564 break 'outer;
1565 // ^^^^^^^^^^^^
1566 break 'inner;
1567 }
1568 break$0 'outer;
1569 // ^^^^^^^^^^^^
1570 break;
1571 }
1572 break;
1573 // ^^^^^
1574 }
1575}
1576"#,
1577 );
1578 }
1579
1580 #[test]
1581 fn test_hl_break_labeled_block() {
1582 check(
1583 r#"
1584fn foo() {
1585 'outer: {
1586 // ^^^^^^^
1587 break;
1588 // ^^^^^
1589 'inner: {
1590 break;
1591 'innermost: {
1592 break 'outer;
1593 // ^^^^^^^^^^^^
1594 break 'inner;
1595 }
1596 break$0 'outer;
1597 // ^^^^^^^^^^^^
1598 break;
1599 }
1600 break;
1601 // ^^^^^
1602 }
1603}
1604"#,
1605 );
1606 }
1607
1608 #[test]
1609 fn test_hl_break_unlabeled_loop() {
1610 check(
1611 r#"
1612fn foo() {
1613 loop {
1614 // ^^^^
1615 break$0;
1616 // ^^^^^
1617 }
1618}
1619"#,
1620 );
1621 }
1622
1623 #[test]
1624 fn test_hl_break_unlabeled_block_in_loop() {
1625 check(
1626 r#"
1627fn foo() {
1628 loop {
1629 // ^^^^
1630 {
1631 break$0;
1632 // ^^^^^
1633 }
1634 }
1635}
1636"#,
1637 );
1638 }
1639
1640 #[test]
1641 fn test_hl_field_shorthand() {
1642 check(
1643 r#"
1644struct Struct { field: u32 }
1645 //^^^^^
1646fn function(field: u32) {
1647 //^^^^^
1648 Struct { field$0 }
1649 //^^^^^ read
1650}
1651"#,
1652 );
1653 }
1654
1655 #[test]
1656 fn test_hl_disabled_ref_local() {
1657 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1658
1659 check_with_config(
1660 r#"
1661fn foo() {
1662 let x$0 = 5;
1663 let y = x * 2;
1664}
1665"#,
1666 config,
1667 );
1668 }
1669
1670 #[test]
1671 fn test_hl_disabled_ref_local_preserved_break() {
1672 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1673
1674 check_with_config(
1675 r#"
1676fn foo() {
1677 let x$0 = 5;
1678 let y = x * 2;
1679
1680 loop {
1681 break;
1682 }
1683}
1684"#,
1685 config.clone(),
1686 );
1687
1688 check_with_config(
1689 r#"
1690fn foo() {
1691 let x = 5;
1692 let y = x * 2;
1693
1694 loop$0 {
1695// ^^^^
1696 break;
1697// ^^^^^
1698 }
1699}
1700"#,
1701 config,
1702 );
1703 }
1704
1705 #[test]
1706 fn test_hl_disabled_ref_local_preserved_yield() {
1707 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1708
1709 check_with_config(
1710 r#"
1711async fn foo() {
1712 let x$0 = 5;
1713 let y = x * 2;
1714
1715 0.await;
1716}
1717"#,
1718 config.clone(),
1719 );
1720
1721 check_with_config(
1722 r#"
1723 async fn foo() {
1724// ^^^^^
1725 let x = 5;
1726 let y = x * 2;
1727
1728 0.await$0;
1729// ^^^^^
1730}
1731"#,
1732 config,
1733 );
1734 }
1735
1736 #[test]
1737 fn test_hl_disabled_ref_local_preserved_exit() {
1738 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1739
1740 check_with_config(
1741 r#"
1742fn foo() -> i32 {
1743 let x$0 = 5;
1744 let y = x * 2;
1745
1746 if true {
1747 return y;
1748 }
1749
1750 0?
1751}
1752"#,
1753 config.clone(),
1754 );
1755
1756 check_with_config(
1757 r#"
1758 fn foo() ->$0 i32 {
1759//^^
1760 let x = 5;
1761 let y = x * 2;
1762
1763 if true {
1764 return y;
1765// ^^^^^^
1766 }
1767
1768 0?
1769// ^
1770"#,
1771 config,
1772 );
1773 }
1774
1775 #[test]
1776 fn test_hl_disabled_break() {
1777 let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
1778
1779 check_with_config(
1780 r#"
1781fn foo() {
1782 loop {
1783 break$0;
1784 }
1785}
1786"#,
1787 config,
1788 );
1789 }
1790
1791 #[test]
1792 fn test_hl_disabled_yield() {
1793 let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
1794
1795 check_with_config(
1796 r#"
1797async$0 fn foo() {
1798 0.await;
1799}
1800"#,
1801 config,
1802 );
1803 }
1804
1805 #[test]
1806 fn test_hl_disabled_exit() {
1807 let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
1808
1809 check_with_config(
1810 r#"
1811fn foo() ->$0 i32 {
1812 if true {
1813 return -1;
1814 }
1815
1816 42
1817}"#,
1818 config,
1819 );
1820 }
1821
1822 #[test]
1823 fn test_hl_multi_local() {
1824 check(
1825 r#"
1826fn foo((
1827 foo$0
1828 //^^^
1829 | foo
1830 //^^^
1831 | foo
1832 //^^^
1833): ()) {
1834 foo;
1835 //^^^read
1836 let foo;
1837}
1838"#,
1839 );
1840 check(
1841 r#"
1842fn foo((
1843 foo
1844 //^^^
1845 | foo$0
1846 //^^^
1847 | foo
1848 //^^^
1849): ()) {
1850 foo;
1851 //^^^read
1852 let foo;
1853}
1854"#,
1855 );
1856 check(
1857 r#"
1858fn foo((
1859 foo
1860 //^^^
1861 | foo
1862 //^^^
1863 | foo
1864 //^^^
1865): ()) {
1866 foo$0;
1867 //^^^read
1868 let foo;
1869}
1870"#,
1871 );
1872 }
1873
1874 #[test]
1875 fn test_hl_trait_impl_methods() {
1876 check(
1877 r#"
1878trait Trait {
1879 fn func$0(self) {}
1880 //^^^^
1881}
1882
1883impl Trait for () {
1884 fn func(self) {}
1885 //^^^^
1886}
1887
1888fn main() {
1889 <()>::func(());
1890 //^^^^
1891 ().func();
1892 //^^^^
1893}
1894"#,
1895 );
1896 check(
1897 r#"
1898trait Trait {
1899 fn func(self) {}
1900}
1901
1902impl Trait for () {
1903 fn func$0(self) {}
1904 //^^^^
1905}
1906
1907fn main() {
1908 <()>::func(());
1909 //^^^^
1910 ().func();
1911 //^^^^
1912}
1913"#,
1914 );
1915 check(
1916 r#"
1917trait Trait {
1918 fn func(self) {}
1919}
1920
1921impl Trait for () {
1922 fn func(self) {}
1923 //^^^^
1924}
1925
1926fn main() {
1927 <()>::func(());
1928 //^^^^
1929 ().func$0();
1930 //^^^^
1931}
1932"#,
1933 );
1934 }
1935
1936 #[test]
1937 fn test_assoc_type_highlighting() {
1938 check(
1939 r#"
1940trait Trait {
1941 type Output;
1942 // ^^^^^^
1943}
1944impl Trait for () {
1945 type Output$0 = ();
1946 // ^^^^^^
1947}
1948"#,
1949 );
1950 }
1951
1952 #[test]
1953 fn test_closure_capture_pipe() {
1954 check(
1955 r#"
1956fn f() {
1957 let x = 1;
1958 // ^
1959 let c = $0|y| x + y;
1960 // ^ read
1961}
1962"#,
1963 );
1964 }
1965
1966 #[test]
1967 fn test_closure_capture_move() {
1968 check(
1969 r#"
1970fn f() {
1971 let x = 1;
1972 // ^
1973 let c = move$0 |y| x + y;
1974 // ^ read
1975}
1976"#,
1977 );
1978 }
1979
1980 #[test]
1981 fn test_trait_highlights_assoc_item_uses() {
1982 check(
1983 r#"
1984trait Super {
1985 type SuperT;
1986}
1987trait Foo: Super {
1988 //^^^
1989 type T;
1990 const C: usize;
1991 fn f() {}
1992 fn m(&self) {}
1993}
1994impl Foo for i32 {
1995 //^^^
1996 type T = i32;
1997 const C: usize = 0;
1998 fn f() {}
1999 fn m(&self) {}
2000}
2001fn f<T: Foo$0>(t: T) {
2002 //^^^
2003 let _: T::SuperT;
2004 //^^^^^^
2005 let _: T::T;
2006 //^
2007 t.m();
2008 //^
2009 T::C;
2010 //^
2011 T::f();
2012 //^
2013}
2014
2015fn f2<T: Foo>(t: T) {
2016 //^^^
2017 let _: T::T;
2018 t.m();
2019 T::C;
2020 T::f();
2021}
2022"#,
2023 );
2024 }
2025
2026 #[test]
2027 fn test_trait_highlights_assoc_item_uses_use_tree() {
2028 check(
2029 r#"
2030use Foo$0;
2031 // ^^^ import
2032trait Super {
2033 type SuperT;
2034}
2035trait Foo: Super {
2036 //^^^
2037 type T;
2038 const C: usize;
2039 fn f() {}
2040 fn m(&self) {}
2041}
2042impl Foo for i32 {
2043 //^^^
2044 type T = i32;
2045 // ^
2046 const C: usize = 0;
2047 // ^
2048 fn f() {}
2049 // ^
2050 fn m(&self) {}
2051 // ^
2052}
2053fn f<T: Foo>(t: T) {
2054 //^^^
2055 let _: T::SuperT;
2056 let _: T::T;
2057 //^
2058 t.m();
2059 //^
2060 T::C;
2061 //^
2062 T::f();
2063 //^
2064}
2065"#,
2066 );
2067 }
2068
2069 #[test]
2070 fn implicit_format_args() {
2071 check(
2072 r#"
2073//- minicore: fmt
2074fn test() {
2075 let a = "foo";
2076 // ^
2077 format_args!("hello {a} {a$0} {}", a);
2078 // ^read
2079 // ^read
2080 // ^read
2081}
2082"#,
2083 );
2084 }
2085
2086 #[test]
2087 fn return_in_macros() {
2088 check(
2089 r#"
2090//- minicore: fn
2091macro_rules! N {
2092 ($i:ident, $x:expr, $blk:expr) => {
2093 for $i in 0..$x {
2094 $blk
2095 }
2096 };
2097}
2098
2099fn main() {
2100 fn f() {
2101 // ^^
2102 N!(i, 5, {
2103 println!("{}", i);
2104 return$0;
2105 // ^^^^^^
2106 });
2107
2108 for i in 1..5 {
2109 return;
2110 // ^^^^^^
2111 }
2112 (|| {
2113 return;
2114 })();
2115 }
2116}
2117"#,
2118 )
2119 }
2120
2121 #[test]
2122 fn return_in_closure() {
2123 check(
2124 r#"
2125macro_rules! N {
2126 ($i:ident, $x:expr, $blk:expr) => {
2127 for $i in 0..$x {
2128 $blk
2129 }
2130 };
2131}
2132
2133fn main() {
2134 fn f() {
2135 N!(i, 5, {
2136 println!("{}", i);
2137 return;
2138 });
2139
2140 for i in 1..5 {
2141 return;
2142 }
2143 (|| {
2144 // ^
2145 return$0;
2146 // ^^^^^^
2147 })();
2148 }
2149}
2150"#,
2151 )
2152 }
2153
2154 #[test]
2155 fn return_in_try() {
2156 check(
2157 r#"
2158fn main() {
2159 fn f() {
2160 // ^^
2161 try {
2162 return$0;
2163 // ^^^^^^
2164 }
2165
2166 return;
2167 // ^^^^^^
2168 }
2169}
2170"#,
2171 )
2172 }
2173
2174 #[test]
2175 fn break_in_try() {
2176 check(
2177 r#"
2178fn main() {
2179 for i in 1..100 {
2180 // ^^^
2181 let x: Result<(), ()> = try {
2182 break$0;
2183 // ^^^^^
2184 };
2185 }
2186}
2187"#,
2188 )
2189 }
2190
2191 #[test]
2192 fn no_highlight_on_return_in_macro_call() {
2193 check(
2194 r#"
2195//- minicore:include
2196//- /lib.rs
2197macro_rules! M {
2198 ($blk:expr) => {
2199 $blk
2200 };
2201}
2202
2203fn main() {
2204 fn f() {
2205 // ^^
2206 M!({ return$0; });
2207 // ^^^^^^
2208 // ^^^^^^^^^^^^^^^
2209
2210 include!("a.rs")
2211 // ^^^^^^^^^^^^^^^^
2212 }
2213}
2214
2215//- /a.rs
2216{
2217 return;
2218}
2219"#,
2220 )
2221 }
2222
2223 #[test]
2224 fn nested_match() {
2225 check(
2226 r#"
2227fn main() {
2228 match$0 0 {
2229 // ^^^^^
2230 0 => match 1 {
2231 1 => 2,
2232 // ^
2233 _ => 3,
2234 // ^
2235 },
2236 _ => 4,
2237 // ^
2238 }
2239}
2240"#,
2241 )
2242 }
2243
2244 #[test]
2245 fn single_arm_highlight() {
2246 check(
2247 r#"
2248fn main() {
2249 match 0 {
2250 0 =>$0 {
2251 // ^^
2252 let x = 1;
2253 x
2254 // ^
2255 }
2256 _ => 2,
2257 }
2258}
2259"#,
2260 )
2261 }
2262
2263 #[test]
2264 fn no_branches_when_disabled() {
2265 let config = HighlightRelatedConfig { branch_exit_points: false, ..ENABLED_CONFIG };
2266 check_with_config(
2267 r#"
2268fn main() {
2269 match$0 0 {
2270 0 => 1,
2271 _ => 2,
2272 }
2273}
2274"#,
2275 config,
2276 );
2277 }
2278
2279 #[test]
2280 fn asm() {
2281 check(
2282 r#"
2283//- minicore: asm
2284#[inline]
2285pub unsafe fn bootstrap() -> ! {
2286 builtin#asm(
2287 "blabla",
2288 "mrs {tmp}, CONTROL",
2289 // ^^^ read
2290 "blabla",
2291 "bics {tmp}, {spsel}",
2292 // ^^^ read
2293 "blabla",
2294 "msr CONTROL, {tmp}",
2295 // ^^^ read
2296 "blabla",
2297 tmp$0 = inout(reg) 0,
2298 // ^^^
2299 aaa = in(reg) 2,
2300 aaa = in(reg) msp,
2301 aaa = in(reg) rv,
2302 options(noreturn, nomem, nostack),
2303 );
2304}
2305"#,
2306 )
2307 }
2308
2309 #[test]
2310 fn complex_arms_highlight() {
2311 check(
2312 r#"
2313fn calculate(n: i32) -> i32 { n * 2 }
2314
2315fn main() {
2316 match$0 Some(1) {
2317 // ^^^^^
2318 Some(x) => match x {
2319 0 => { let y = x; y },
2320 // ^
2321 1 => calculate(x),
2322 //^^^^^^^^^^^^
2323 _ => (|| 6)(),
2324 // ^^^^^^^^
2325 },
2326 None => loop {
2327 break 5;
2328 // ^^^^^^^
2329 },
2330 }
2331}
2332"#,
2333 )
2334 }
2335
2336 #[test]
2337 fn match_in_macro_highlight() {
2338 check(
2339 r#"
2340macro_rules! M {
2341 ($e:expr) => { $e };
2342}
2343
2344fn main() {
2345 M!{
2346 match$0 Some(1) {
2347 // ^^^^^
2348 Some(x) => x,
2349 // ^
2350 None => 0,
2351 // ^
2352 }
2353 }
2354}
2355"#,
2356 )
2357 }
2358
2359 #[test]
2360 fn match_in_macro_highlight_2() {
2361 check(
2362 r#"
2363macro_rules! match_ast {
2364 (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
2365
2366 (match ($node:expr) {
2367 $( $( $path:ident )::+ ($it:pat) => $res:expr, )*
2368 _ => $catch_all:expr $(,)?
2369 }) => {{
2370 $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
2371 { $catch_all }
2372 }};
2373}
2374
2375fn main() {
2376 match_ast! {
2377 match$0 Some(1) {
2378 Some(x) => x,
2379 }
2380 }
2381}
2382 "#,
2383 );
2384 }
2385
2386 #[test]
2387 fn nested_if_else() {
2388 check(
2389 r#"
2390fn main() {
2391 if$0 true {
2392 // ^^
2393 if false {
2394 1
2395 // ^
2396 } else {
2397 2
2398 // ^
2399 }
2400 } else {
2401 3
2402 // ^
2403 }
2404}
2405"#,
2406 )
2407 }
2408
2409 #[test]
2410 fn if_else_if_highlight() {
2411 check(
2412 r#"
2413fn main() {
2414 if$0 true {
2415 // ^^
2416 1
2417 // ^
2418 } else if false {
2419 // ^^
2420 2
2421 // ^
2422 } else {
2423 3
2424 // ^
2425 }
2426}
2427"#,
2428 )
2429 }
2430
2431 #[test]
2432 fn complex_if_branches() {
2433 check(
2434 r#"
2435fn calculate(n: i32) -> i32 { n * 2 }
2436
2437fn main() {
2438 if$0 true {
2439 // ^^
2440 let x = 5;
2441 calculate(x)
2442 // ^^^^^^^^^^^^
2443 } else if false {
2444 // ^^
2445 (|| 10)()
2446 // ^^^^^^^^^
2447 } else {
2448 loop {
2449 break 15;
2450 // ^^^^^^^^
2451 }
2452 }
2453}
2454"#,
2455 )
2456 }
2457
2458 #[test]
2459 fn if_in_macro_highlight() {
2460 check(
2461 r#"
2462macro_rules! M {
2463 ($e:expr) => { $e };
2464}
2465
2466fn main() {
2467 M!{
2468 if$0 true {
2469 // ^^
2470 5
2471 // ^
2472 } else {
2473 10
2474 // ^^
2475 }
2476 }
2477}
2478"#,
2479 )
2480 }
2481
2482 #[test]
2483 fn match_in_macro() {
2484 check(
2486 r#"
2487macro_rules! M {
2488 (match) => { 1 };
2489}
2490
2491fn main() {
2492 match Some(1) {
2493 Some(x) => x,
2494 None => {
2495 M!(match$0)
2496 }
2497 }
2498}
2499 "#,
2500 )
2501 }
2502
2503 #[test]
2504 fn labeled_block_tail_expr() {
2505 check(
2506 r#"
2507fn foo() {
2508 'a: {
2509 // ^^^
2510 if true { break$0 'a 0; }
2511 // ^^^^^^^^
2512 5
2513 // ^
2514 }
2515}
2516"#,
2517 );
2518 }
2519
2520 #[test]
2521 fn labeled_block_tail_expr_2() {
2522 check(
2523 r#"
2524fn foo() {
2525 let _ = 'b$0lk: {
2526 // ^^^^
2527 let x = 1;
2528 if true { break 'blk 42; }
2529 // ^^^^
2530 if false { break 'blk 24; }
2531 // ^^^^
2532 100
2533 // ^^^
2534 };
2535}
2536"#,
2537 );
2538 }
2539
2540 #[test]
2541 fn different_unsafe_block() {
2542 check(
2543 r#"
2544fn main() {
2545 unsafe$0 {
2546 // ^^^^^^
2547 *(0 as *const u8)
2548 // ^^^^^^^^^^^^^^^^^
2549 };
2550 unsafe { *(1 as *const u8) };
2551 unsafe { *(2 as *const u8) };
2552}
2553 "#,
2554 );
2555 }
2556
2557 #[test]
2558 fn async_fn_param() {
2559 check(
2560 r#"
2561async fn get_double_async(num$0: u32) -> u32 {
2562 // ^^^
2563 num
2564 // ^^^ read
2565}
2566 "#,
2567 );
2568 check(
2569 r#"
2570async fn get_double_async((num$0,): (u32,)) -> u32 {
2571 // ^^^
2572 num
2573 // ^^^ read
2574}
2575 "#,
2576 );
2577 }
2578}