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