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