File is large — showing lines 1–2,000 of 2,579.
1use std::iter;23use hir::{EditionedFileId, FilePosition, FileRange, HirFileId, InFile, Semantics};4use ide_db::{5 FxHashMap, FxHashSet, RootDatabase,6 base_db::SourceDatabase,7 defs::{Definition, IdentClass},8 helpers::pick_best_token,9 search::{FileReference, ReferenceCategory, SearchScope},10 syntax_helpers::node_ext::{11 eq_label_lt, find_loops, for_each_tail_expr, full_path_of_name_ref,12 is_closure_or_blk_with_modif, preorder_expr_with_ctx_checker,13 },14};15use syntax::{16 AstNode,17 SyntaxKind::{self, IDENT, INT_NUMBER},18 SyntaxToken, T, TextRange, WalkEvent,19 ast::{self, HasLoopBody},20 match_ast,21};2223use crate::{NavigationTarget, TryToNav, goto_definition, navigation_target::ToNav};2425#[derive(PartialEq, Eq, Hash)]26pub struct HighlightedRange {27 pub range: TextRange,28 // FIXME: This needs to be more precise. Reference category makes sense only29 // for references, but we also have defs. And things like exit points are30 // neither.31 pub category: ReferenceCategory,32}3334#[derive(Default, Clone)]35pub struct HighlightRelatedConfig {36 pub references: bool,37 pub exit_points: bool,38 pub break_points: bool,39 pub closure_captures: bool,40 pub yield_points: bool,41 pub branch_exit_points: bool,42}4344type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;4546// Feature: Highlight Related47//48// Highlights constructs related to the thing under the cursor:49//50// 1. if on an identifier, highlights all references to that identifier in the current file51// * additionally, if the identifier is a trait in a where clause, type parameter trait bound or use item, highlights all references to that trait's assoc items in the corresponding scope52// 1. if on an `async` or `await` token, highlights all yield points for that async context53// 1. if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context54// 1. if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context55// 1. if on a `move` or `|` token that belongs to a closure, highlights all captures of the closure.56//57// Note: `?`, `|` and `->` do not currently trigger this behavior in the VSCode editor.58pub(crate) fn highlight_related(59 sema: &Semantics<'_, RootDatabase>,60 config: HighlightRelatedConfig,61 ide_db::FilePosition { offset, file_id }: ide_db::FilePosition,62) -> Option<Vec<HighlightedRange>> {63 let _p = tracing::info_span!("highlight_related").entered();64 let file_id = sema.attach_first_edition(file_id);65 let syntax = sema.parse(file_id).syntax().clone();6667 let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {68 T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`69 T![->] | T![=>] => 4,70 kind if kind.is_keyword(file_id.edition(sema.db)) => 3,71 IDENT | INT_NUMBER => 2,72 T![|] => 1,73 _ => 0,74 })?;75 // most if not all of these should be re-implemented with information seeded from hir76 match token.kind() {77 T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {78 highlight_exit_points(sema, token).remove(&file_id)79 }80 T![fn] | T![return] | T![->] if config.exit_points => {81 highlight_exit_points(sema, token).remove(&file_id)82 }83 T![match] | T![=>] | T![if] if config.branch_exit_points => {84 highlight_branch_exit_points(sema, token).remove(&file_id)85 }86 T![await] | T![async] if config.yield_points => {87 highlight_yield_points(sema, token).remove(&file_id)88 }89 T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {90 highlight_break_points(sema, token).remove(&file_id)91 }92 T![break] | T![loop] | T![while] | T![continue] if config.break_points => {93 highlight_break_points(sema, token).remove(&file_id)94 }95 T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {96 highlight_unsafe_points(sema, token).remove(&file_id)97 }98 T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),99 T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),100 _ if config.references => {101 highlight_references(sema, token, FilePosition { file_id, offset })102 }103 _ => None,104 }105}106107fn highlight_closure_captures(108 sema: &Semantics<'_, RootDatabase>,109 token: SyntaxToken,110 file_id: EditionedFileId,111) -> Option<Vec<HighlightedRange>> {112 let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;113 let search_range = closure.body()?.syntax().text_range();114 let ty = &sema.type_of_expr(&closure.into())?.original;115 let c = ty.as_closure()?;116 Some(117 c.captured_items(sema.db)118 .into_iter()119 .map(|capture| capture.local())120 .flat_map(|local| {121 let usages = Definition::Local(local)122 .usages(sema)123 .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))124 .include_self_refs()125 .all()126 .references127 .remove(&file_id)128 .into_iter()129 .flatten()130 .map(|FileReference { category, range, .. }| HighlightedRange {131 range,132 category,133 });134 let category = if local.is_mut(sema.db) {135 ReferenceCategory::WRITE136 } else {137 ReferenceCategory::empty()138 };139 local140 .sources(sema.db)141 .into_iter()142 .flat_map(|x| x.to_nav(sema.db))143 .filter(|decl| decl.file_id == file_id.file_id(sema.db))144 .filter_map(|decl| decl.focus_range)145 .map(move |range| HighlightedRange { range, category })146 .chain(usages)147 })148 .collect(),149 )150}151152fn highlight_references(153 sema: &Semantics<'_, RootDatabase>,154 token: SyntaxToken,155 FilePosition { file_id, offset }: FilePosition,156) -> Option<Vec<HighlightedRange>> {157 let defs = if let Some((range, _, _, resolution)) =158 sema.check_for_format_args_template(token.clone(), offset)159 {160 match resolution.map(Definition::from) {161 Some(def) => iter::once(def).collect(),162 None => {163 return Some(vec![HighlightedRange {164 range,165 category: ReferenceCategory::empty(),166 }]);167 }168 }169 } else {170 find_defs(sema, token.clone())171 };172 let usages = defs173 .iter()174 .filter_map(|&d| {175 d.usages(sema)176 .in_scope(&SearchScope::single_file(file_id))177 .include_self_refs()178 .all()179 .references180 .remove(&file_id)181 })182 .flatten()183 .map(|FileReference { category, range, .. }| HighlightedRange { range, category });184 let mut res = FxHashSet::default();185 for &def in &defs {186 // highlight trait usages187 if let Definition::Trait(t) = def {188 let trait_item_use_scope = (|| {189 let name_ref = token.parent().and_then(ast::NameRef::cast)?;190 let path = full_path_of_name_ref(&name_ref)?;191 let parent = path.syntax().parent()?;192 match_ast! {193 match parent {194 ast::UseTree(it) => it.syntax().ancestors().find(|it| {195 ast::SourceFile::can_cast(it.kind()) || ast::Module::can_cast(it.kind())196 }).zip(Some(true)),197 ast::PathType(it) => it198 .syntax()199 .ancestors()200 .nth(2)201 .and_then(ast::TypeBoundList::cast)?202 .syntax()203 .parent()204 .filter(|it| ast::WhereClause::can_cast(it.kind()) || ast::TypeParam::can_cast(it.kind()))?205 .ancestors()206 .find(|it| {207 ast::Item::can_cast(it.kind())208 }).zip(Some(false)),209 _ => None,210 }211 }212 })();213 if let Some((trait_item_use_scope, use_tree)) = trait_item_use_scope {214 res.extend(215 if use_tree { t.items(sema.db) } else { t.items_with_supertraits(sema.db) }216 .into_iter()217 .filter_map(|item| {218 Definition::from(item)219 .usages(sema)220 .set_scope(Some(&SearchScope::file_range(FileRange {221 file_id,222 range: trait_item_use_scope.text_range(),223 })))224 .include_self_refs()225 .all()226 .references227 .remove(&file_id)228 })229 .flatten()230 .map(|FileReference { category, range, .. }| HighlightedRange {231 range,232 category,233 }),234 );235 }236 }237238 // highlight the tail expr of the labelled block239 if matches!(def, Definition::Label(_)) {240 let label = token.parent_ancestors().nth(1).and_then(ast::Label::cast);241 if let Some(block) =242 label.and_then(|label| label.syntax().parent()).and_then(ast::BlockExpr::cast)243 {244 for_each_tail_expr(&block.into(), &mut |tail| {245 if !matches!(tail, ast::Expr::BreakExpr(_)) {246 res.insert(HighlightedRange {247 range: tail.syntax().text_range(),248 category: ReferenceCategory::empty(),249 });250 }251 });252 }253 }254255 // highlight the defs themselves256 match def {257 Definition::Local(local) => {258 let category = if local.is_mut(sema.db) {259 ReferenceCategory::WRITE260 } else {261 ReferenceCategory::empty()262 };263 local264 .sources(sema.db)265 .into_iter()266 .flat_map(|x| x.to_nav(sema.db))267 .filter(|decl| decl.file_id == file_id.file_id(sema.db))268 .filter_map(|decl| decl.focus_range)269 .map(|range| HighlightedRange { range, category })270 .for_each(|x| {271 res.insert(x);272 });273 }274 def => {275 let navs = match def {276 Definition::Module(module) => {277 NavigationTarget::from_module_to_decl(sema.db, module)278 }279 def => match def.try_to_nav(sema) {280 Some(it) => it,281 None => continue,282 },283 };284 for nav in navs {285 if nav.file_id != file_id.file_id(sema.db) {286 continue;287 }288 let hl_range = nav.focus_range.map(|range| {289 let category = if matches!(def, Definition::Local(l) if l.is_mut(sema.db)) {290 ReferenceCategory::WRITE291 } else {292 ReferenceCategory::empty()293 };294 HighlightedRange { range, category }295 });296 if let Some(hl_range) = hl_range {297 res.insert(hl_range);298 }299 }300 }301 }302 }303304 res.extend(usages);305 if res.is_empty() { None } else { Some(res.into_iter().collect()) }306}307308pub(crate) fn highlight_branch_exit_points(309 sema: &Semantics<'_, RootDatabase>,310 token: SyntaxToken,311) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {312 let mut highlights: HighlightMap = FxHashMap::default();313314 let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {315 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {316 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };317 highlights.entry(file_id).or_default().insert(hrange);318 }319 };320321 let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {322 let Some(tail) = tail else {323 return;324 };325326 for_each_tail_expr(&tail, &mut |tail| {327 let file_id = sema.hir_file_for(tail.syntax());328 let range = tail.syntax().text_range();329 push_to_highlights(file_id, Some(range), highlights);330 });331 };332333 let nodes = goto_definition::find_branch_root(sema, &token).into_iter();334 match token.kind() {335 T![match] => {336 for match_expr in nodes.filter_map(ast::MatchExpr::cast) {337 let file_id = sema.hir_file_for(match_expr.syntax());338 let range = match_expr.match_token().map(|token| token.text_range());339 push_to_highlights(file_id, range, &mut highlights);340341 let Some(arm_list) = match_expr.match_arm_list() else {342 continue;343 };344 for arm in arm_list.arms() {345 push_tail_expr(arm.expr(), &mut highlights);346 }347 }348 }349 T![=>] => {350 for arm in nodes.filter_map(ast::MatchArm::cast) {351 let file_id = sema.hir_file_for(arm.syntax());352 let range = arm.fat_arrow_token().map(|token| token.text_range());353 push_to_highlights(file_id, range, &mut highlights);354355 push_tail_expr(arm.expr(), &mut highlights);356 }357 }358 T![if] => {359 for mut if_to_process in nodes.map(ast::IfExpr::cast) {360 while let Some(cur_if) = if_to_process.take() {361 let file_id = sema.hir_file_for(cur_if.syntax());362363 let if_kw_range = cur_if.if_token().map(|token| token.text_range());364 push_to_highlights(file_id, if_kw_range, &mut highlights);365366 if let Some(then_block) = cur_if.then_branch() {367 push_tail_expr(Some(then_block.into()), &mut highlights);368 }369370 match cur_if.else_branch() {371 Some(ast::ElseBranch::Block(else_block)) => {372 push_tail_expr(Some(else_block.into()), &mut highlights);373 if_to_process = None;374 }375 Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),376 None => if_to_process = None,377 }378 }379 }380 }381 _ => {}382 }383384 highlights385 .into_iter()386 .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))387 .collect()388}389390fn hl_exit_points(391 sema: &Semantics<'_, RootDatabase>,392 def_token: Option<SyntaxToken>,393 body: ast::Expr,394) -> Option<HighlightMap> {395 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();396397 let mut push_to_highlights = |file_id, range| {398 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {399 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };400 highlights.entry(file_id).or_default().insert(hrange);401 }402 };403404 if let Some(tok) = def_token {405 let file_id = sema.hir_file_for(&tok.parent()?);406 let range = Some(tok.text_range());407 push_to_highlights(file_id, range);408 }409410 WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {411 let file_id = sema.hir_file_for(expr.syntax());412413 let range = match &expr {414 ast::Expr::TryExpr(try_) => try_.question_mark_token().map(|token| token.text_range()),415 ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_)416 if sema.type_of_expr(&expr).is_some_and(|ty| ty.original.is_never()) =>417 {418 Some(expr.syntax().text_range())419 }420 _ => None,421 };422423 push_to_highlights(file_id, range);424 });425426 // We should handle `return` separately, because when it is used in a `try` block,427 // it will exit the outside function instead of the block itself.428 WalkExpandedExprCtx::new(sema)429 .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)430 .walk(&body, &mut |_, expr| {431 let file_id = sema.hir_file_for(expr.syntax());432433 let range = match &expr {434 ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()),435 _ => None,436 };437438 push_to_highlights(file_id, range);439 });440441 let tail = match body {442 ast::Expr::BlockExpr(b) => b.tail_expr(),443 e => Some(e),444 };445446 if let Some(tail) = tail {447 for_each_tail_expr(&tail, &mut |tail| {448 let file_id = sema.hir_file_for(tail.syntax());449 let range = match tail {450 ast::Expr::BreakExpr(b) => b451 .break_token()452 .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),453 _ => tail.syntax().text_range(),454 };455 push_to_highlights(file_id, Some(range));456 });457 }458 Some(highlights)459}460461// If `file_id` is None,462pub(crate) fn highlight_exit_points(463 sema: &Semantics<'_, RootDatabase>,464 token: SyntaxToken,465) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {466 let mut res = FxHashMap::default();467 for def in goto_definition::find_fn_or_blocks(sema, &token) {468 let new_map = match_ast! {469 match def {470 ast::Fn(fn_) => fn_.body().and_then(|body| hl_exit_points(sema, fn_.fn_token(), body.into())),471 ast::ClosureExpr(closure) => {472 let pipe_tok = closure.param_list().and_then(|p| p.pipe_token());473 closure.body().and_then(|body| hl_exit_points(sema, pipe_tok, body))474 },475 ast::BlockExpr(blk) => match blk.modifier() {476 Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()),477 Some(ast::BlockModifier::Try { try_token: t, .. }) if token.kind() != T![return] => {478 hl_exit_points(sema, Some(t), blk.into())479 },480 _ => continue,481 },482 _ => continue,483 }484 };485 merge_map(&mut res, new_map);486 }487488 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()489}490491pub(crate) fn highlight_break_points(492 sema: &Semantics<'_, RootDatabase>,493 token: SyntaxToken,494) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {495 pub(crate) fn hl(496 sema: &Semantics<'_, RootDatabase>,497 cursor_token_kind: SyntaxKind,498 loop_token: Option<SyntaxToken>,499 label: Option<ast::Label>,500 expr: ast::Expr,501 ) -> Option<HighlightMap> {502 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();503504 let mut push_to_highlights = |file_id, range| {505 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {506 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };507 highlights.entry(file_id).or_default().insert(hrange);508 }509 };510511 let label_lt = label.as_ref().and_then(|it| it.lifetime());512513 if let Some(range) = cover_range(514 loop_token.as_ref().map(|tok| tok.text_range()),515 label.as_ref().map(|it| it.syntax().text_range()),516 ) {517 let file_id = loop_token518 .and_then(|tok| Some(sema.hir_file_for(&tok.parent()?)))519 .unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax()));520 push_to_highlights(file_id, Some(range));521 }522523 WalkExpandedExprCtx::new(sema)524 .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure)525 .walk(&expr, &mut |depth, expr| {526 let file_id = sema.hir_file_for(expr.syntax());527528 // Only highlight the `break`s for `break` and `continue`s for `continue`529 let (token, token_lt) = match expr {530 ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => {531 (b.break_token(), b.lifetime())532 }533 ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => {534 (c.continue_token(), c.lifetime())535 }536 _ => return,537 };538539 if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) {540 return;541 }542543 let text_range = cover_range(544 token.map(|it| it.text_range()),545 token_lt.map(|it| it.syntax().text_range()),546 );547548 push_to_highlights(file_id, text_range);549 });550551 if matches!(expr, ast::Expr::BlockExpr(_)) {552 for_each_tail_expr(&expr, &mut |tail| {553 if matches!(tail, ast::Expr::BreakExpr(_)) {554 return;555 }556557 let file_id = sema.hir_file_for(tail.syntax());558 let range = tail.syntax().text_range();559 push_to_highlights(file_id, Some(range));560 });561 }562563 Some(highlights)564 }565566 let Some(loops) = find_loops(sema, &token) else {567 return FxHashMap::default();568 };569570 let mut res = FxHashMap::default();571 let token_kind = token.kind();572 for expr in loops {573 let new_map = match &expr {574 ast::Expr::LoopExpr(l) => hl(sema, token_kind, l.loop_token(), l.label(), expr),575 ast::Expr::ForExpr(f) => hl(sema, token_kind, f.for_token(), f.label(), expr),576 ast::Expr::WhileExpr(w) => hl(sema, token_kind, w.while_token(), w.label(), expr),577 ast::Expr::BlockExpr(e) => hl(sema, token_kind, None, e.label(), expr),578 _ => continue,579 };580 merge_map(&mut res, new_map);581 }582583 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()584}585586pub(crate) fn highlight_yield_points(587 sema: &Semantics<'_, RootDatabase>,588 token: SyntaxToken,589) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {590 fn hl(591 sema: &Semantics<'_, RootDatabase>,592 async_token: Option<SyntaxToken>,593 body: Option<ast::Expr>,594 ) -> Option<HighlightMap> {595 let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();596597 let mut push_to_highlights = |file_id, range| {598 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {599 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };600 highlights.entry(file_id).or_default().insert(hrange);601 }602 };603604 let async_token = async_token?;605 let async_tok_file_id = sema.hir_file_for(&async_token.parent()?);606 push_to_highlights(async_tok_file_id, Some(async_token.text_range()));607608 let Some(body) = body else {609 return Some(highlights);610 };611612 WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| {613 let file_id = sema.hir_file_for(expr.syntax());614615 let text_range = match expr {616 ast::Expr::AwaitExpr(expr) => expr.await_token(),617 ast::Expr::ReturnExpr(expr) => expr.return_token(),618 _ => None,619 }620 .map(|it| it.text_range());621622 push_to_highlights(file_id, text_range);623 });624625 Some(highlights)626 }627628 let mut res = FxHashMap::default();629 for anc in goto_definition::find_fn_or_blocks(sema, &token) {630 let new_map = match_ast! {631 match anc {632 ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),633 ast::BlockExpr(block_expr) => {634 let Some(async_token) = block_expr.async_token() else {635 continue;636 };637638 // Async blocks act similar to closures. So we want to639 // highlight their exit points too, but only if we are on640 // the async token.641 if async_token == token {642 let exit_points = hl_exit_points(643 sema,644 Some(async_token.clone()),645 block_expr.clone().into(),646 );647 merge_map(&mut res, exit_points);648 }649650 hl(sema, Some(async_token), Some(block_expr.into()))651 },652 ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()),653 _ => continue,654 }655 };656 merge_map(&mut res, new_map);657 }658659 res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect()660}661662fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {663 match (r0, r1) {664 (Some(r0), Some(r1)) => Some(r0.cover(r1)),665 (Some(range), None) => Some(range),666 (None, Some(range)) => Some(range),667 (None, None) => None,668 }669}670671fn find_defs<'db>(672 sema: &Semantics<'db, RootDatabase>,673 token: SyntaxToken,674) -> FxHashSet<Definition<'db>> {675 sema.descend_into_macros_exact(token)676 .into_iter()677 .filter_map(|token| IdentClass::classify_token(sema, &token))678 .flat_map(IdentClass::definitions_no_ops)679 .collect()680}681682fn original_frange(683 db: &dyn SourceDatabase,684 file_id: HirFileId,685 text_range: Option<TextRange>,686) -> Option<FileRange> {687 InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)688}689690fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {691 let Some(new) = new else {692 return;693 };694 new.into_iter().for_each(|(file_id, ranges)| {695 res.entry(file_id).or_default().extend(ranges);696 });697}698699/// Preorder walk all the expression's child expressions.700/// For macro calls, the callback will be called on the expanded expressions after701/// visiting the macro call itself.702struct WalkExpandedExprCtx<'a, 'db> {703 sema: &'a Semantics<'db, RootDatabase>,704 depth: usize,705 check_ctx: &'static dyn Fn(&ast::Expr) -> bool,706}707708impl<'a, 'db> WalkExpandedExprCtx<'a, 'db> {709 fn new(sema: &'a Semantics<'db, RootDatabase>) -> Self {710 Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif }711 }712713 fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self {714 Self { check_ctx, ..*self }715 }716717 fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) {718 preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent<ast::Expr>| {719 match ev {720 syntax::WalkEvent::Enter(expr) => {721 cb(self.depth, expr.clone());722723 if Self::should_change_depth(&expr) {724 self.depth += 1;725 }726727 if let ast::Expr::MacroExpr(expr) = expr728 && let Some(expanded) =729 expr.macro_call().and_then(|call| self.sema.expand_macro_call(&call))730 {731 match_ast! {732 match (expanded.value) {733 ast::MacroStmts(it) => {734 self.handle_expanded(it, cb);735 },736 ast::Expr(it) => {737 self.walk(&it, cb);738 },739 _ => {}740 }741 }742 }743 }744 syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => {745 self.depth -= 1;746 }747 _ => {}748 }749 false750 })751 }752753 fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) {754 if let Some(expr) = expanded.expr() {755 self.walk(&expr, cb);756 }757758 for stmt in expanded.statements() {759 if let ast::Stmt::ExprStmt(stmt) = stmt760 && let Some(expr) = stmt.expr()761 {762 self.walk(&expr, cb);763 }764 }765 }766767 fn should_change_depth(expr: &ast::Expr) -> bool {768 match expr {769 ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true,770 ast::Expr::BlockExpr(blk) if blk.label().is_some() => true,771 _ => false,772 }773 }774775 fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool {776 match expr {777 ast::Expr::BlockExpr(b) => matches!(778 b.modifier(),779 Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_))780 ),781 ast::Expr::ClosureExpr(_) => true,782 _ => false,783 }784 }785}786787pub(crate) fn highlight_unsafe_points(788 sema: &Semantics<'_, RootDatabase>,789 token: SyntaxToken,790) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {791 fn hl(792 sema: &Semantics<'_, RootDatabase>,793 unsafe_token: &SyntaxToken,794 block_expr: Option<ast::BlockExpr>,795 ) -> Option<FxHashMap<EditionedFileId, Vec<HighlightedRange>>> {796 let mut highlights: FxHashMap<EditionedFileId, Vec<_>> = FxHashMap::default();797798 let mut push_to_highlights = |file_id, range| {799 if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {800 let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };801 highlights.entry(file_id).or_default().push(hrange);802 }803 };804805 // highlight unsafe keyword itself806 let unsafe_token_file_id = sema.hir_file_for(&unsafe_token.parent()?);807 push_to_highlights(unsafe_token_file_id, Some(unsafe_token.text_range()));808809 // highlight unsafe operations810 if let Some(block) = block_expr {811 let unsafe_ops = sema.get_unsafe_ops_for_unsafe_block(block);812 for unsafe_op in unsafe_ops {813 push_to_highlights(unsafe_op.file_id, Some(unsafe_op.value.text_range()));814 }815 }816817 Some(highlights)818 }819820 hl(sema, &token, token.parent().and_then(ast::BlockExpr::cast)).unwrap_or_default()821}822823#[cfg(test)]824mod tests {825 use itertools::Itertools;826827 use crate::fixture;828829 use super::*;830831 const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {832 break_points: true,833 exit_points: true,834 references: true,835 closure_captures: true,836 yield_points: true,837 branch_exit_points: true,838 };839840 #[track_caller]841 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {842 check_with_config(ra_fixture, ENABLED_CONFIG);843 }844845 #[track_caller]846 fn check_with_config(847 #[rust_analyzer::rust_fixture] ra_fixture: &str,848 config: HighlightRelatedConfig,849 ) {850 let (analysis, pos, annotations) = fixture::annotations(ra_fixture);851852 let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();853854 let mut expected =855 annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();856857 let mut actual: Vec<(TextRange, String)> = hls858 .into_iter()859 .map(|hl| {860 (861 hl.range,862 hl.category.iter_names().map(|(name, _flag)| name.to_lowercase()).join(","),863 )864 })865 .collect();866 actual.sort_by_key(|(range, _)| range.start());867 expected.sort_by_key(|(range, _)| range.start());868869 assert_eq!(expected, actual);870 }871872 #[test]873 fn test_hl_unsafe_block() {874 check(875 r#"876fn foo() {877 unsafe fn this_is_unsafe_function() {}878879 unsa$0fe {880 //^^^^^^881 let raw_ptr = &42 as *const i32;882 let val = *raw_ptr;883 //^^^^^^^^884885 let mut_ptr = &mut 5 as *mut i32;886 *mut_ptr = 10;887 //^^^^^^^^888889 this_is_unsafe_function();890 //^^^^^^^^^^^^^^^^^^^^^^^^^891 }892893}894"#,895 );896 }897898 #[test]899 fn test_hl_tuple_fields() {900 check(901 r#"902struct Tuple(u32, u32);903904fn foo(t: Tuple) {905 t.0$0;906 // ^ read907 t.0;908 // ^ read909}910"#,911 );912 }913914 #[test]915 fn test_hl_module() {916 check(917 r#"918//- /lib.rs919mod foo$0;920 // ^^^921//- /foo.rs922struct Foo;923"#,924 );925 }926927 #[test]928 fn test_hl_self_in_crate_root() {929 check(930 r#"931use crate$0;932 //^^^^^ import933use self;934 //^^^^ import935mod __ {936 use super;937 //^^^^^ import938}939"#,940 );941 check(942 r#"943//- /main.rs crate:main deps:lib944use lib$0;945 //^^^ import946//- /lib.rs crate:lib947"#,948 );949 }950951 #[test]952 fn test_hl_self_in_module() {953 check(954 r#"955//- /lib.rs956mod foo;957//- /foo.rs958use self$0;959 // ^^^^ import960"#,961 );962 }963964 #[test]965 fn test_hl_local() {966 check(967 r#"968fn foo() {969 let mut bar = 3;970 // ^^^ write971 bar$0;972 // ^^^ read973}974"#,975 );976 }977978 #[test]979 fn test_hl_local_in_attr() {980 check(981 r#"982//- proc_macros: identity983#[proc_macros::identity]984fn foo() {985 let mut bar = 3;986 // ^^^ write987 bar$0;988 // ^^^ read989}990"#,991 );992 }993994 #[test]995 fn test_multi_macro_usage() {996 check(997 r#"998macro_rules! foo {999 ($ident:ident) => {1000 fn $ident() -> $ident { loop {} }1001 struct $ident;1002 }1003}10041005foo!(bar$0);1006 // ^^^1007fn foo() {1008 let bar: bar = bar();1009 // ^^^1010 // ^^^1011}1012"#,1013 );1014 check(1015 r#"1016macro_rules! foo {1017 ($ident:ident) => {1018 fn $ident() -> $ident { loop {} }1019 struct $ident;1020 }1021}10221023foo!(bar);1024 // ^^^1025fn foo() {1026 let bar: bar$0 = bar();1027 // ^^^1028}1029"#,1030 );1031 }10321033 #[test]1034 fn test_hl_yield_points() {1035 check(1036 r#"1037pub async fn foo() {1038 // ^^^^^1039 let x = foo()1040 .await$01041 // ^^^^^1042 .await;1043 // ^^^^^1044 || { 0.await };1045 (async { 0.await }).await1046 // ^^^^^1047}1048"#,1049 );1050 }10511052 #[test]1053 fn test_hl_yield_points2() {1054 check(1055 r#"1056pub async$0 fn foo() {1057 // ^^^^^1058 let x = foo()1059 .await1060 // ^^^^^1061 .await;1062 // ^^^^^1063 || { 0.await };1064 (async { 0.await }).await1065 // ^^^^^1066}1067"#,1068 );1069 }10701071 #[test]1072 fn test_hl_exit_points_of_async_blocks() {1073 check(1074 r#"1075pub fn foo() {1076 let x = async$0 {1077 // ^^^^^1078 0.await;1079 // ^^^^^1080 0?;1081 // ^1082 return 0;1083 // ^^^^^^1084 01085 // ^1086 };1087}1088"#,1089 );1090 }10911092 #[test]1093 fn test_hl_let_else_yield_points() {1094 check(1095 r#"1096pub async fn foo() {1097 // ^^^^^1098 let x = foo()1099 .await$01100 // ^^^^^1101 .await;1102 // ^^^^^1103 || { 0.await };1104 let Some(_) = None else {1105 foo().await1106 // ^^^^^1107 };1108 (async { 0.await }).await1109 // ^^^^^1110}1111"#,1112 );1113 }11141115 #[test]1116 fn test_hl_yield_nested_fn() {1117 check(1118 r#"1119async fn foo() {1120 async fn foo2() {1121 // ^^^^^1122 async fn foo3() {1123 0.await1124 }1125 0.await$01126 // ^^^^^1127 }1128 0.await1129}1130"#,1131 );1132 }11331134 #[test]1135 fn test_hl_yield_nested_async_blocks() {1136 check(1137 r#"1138async fn foo() {1139 (async {1140 // ^^^^^1141 (async { 0.await }).await$01142 // ^^^^^1143 }).await;1144}1145"#,1146 );1147 }11481149 #[test]1150 fn test_hl_exit_points() {1151 check(1152 r#"1153 fn foo() -> u32 {1154//^^1155 if true {1156 return$0 0;1157 // ^^^^^^1158 }11591160 0?;1161 // ^1162 0xDEAD_BEEF1163 // ^^^^^^^^^^^1164}1165"#,1166 );1167 }11681169 #[test]1170 fn test_hl_exit_points2() {1171 check(1172 r#"1173 fn foo() ->$0 u32 {1174//^^1175 if true {1176 return 0;1177 // ^^^^^^1178 }11791180 0?;1181 // ^1182 0xDEAD_BEEF1183 // ^^^^^^^^^^^1184}1185"#,1186 );1187 }11881189 #[test]1190 fn test_hl_exit_points3() {1191 check(1192 r#"1193 fn$0 foo() -> u32 {1194//^^1195 if true {1196 return 0;1197 // ^^^^^^1198 }11991200 0?;1201 // ^1202 0xDEAD_BEEF1203 // ^^^^^^^^^^^1204}1205"#,1206 );1207 }12081209 #[test]1210 fn test_hl_let_else_exit_points() {1211 check(1212 r#"1213 fn$0 foo() -> u32 {1214//^^1215 let Some(bar) = None else {1216 return 0;1217 // ^^^^^^1218 };12191220 0?;1221 // ^1222 0xDEAD_BEEF1223 // ^^^^^^^^^^^1224}1225"#,1226 );1227 }12281229 #[test]1230 fn test_hl_prefer_ref_over_tail_exit() {1231 check(1232 r#"1233fn foo() -> u32 {1234// ^^^1235 if true {1236 return 0;1237 }12381239 0?;12401241 foo$0()1242 // ^^^1243}1244"#,1245 );1246 }12471248 #[test]1249 fn test_hl_never_call_is_exit_point() {1250 check(1251 r#"1252struct Never;1253impl Never {1254 fn never(self) -> ! { loop {} }1255}1256macro_rules! never {1257 () => { never() }1258 // ^^^^^^^1259}1260fn never() -> ! { loop {} }1261 fn foo() ->$0 u32 {1262//^^1263 never();1264 // ^^^^^^^1265 never!();1266 // ^^^^^^^^12671268 Never.never();1269 // ^^^^^^^^^^^^^12701271 01272 // ^1273}1274"#,1275 );1276 }12771278 #[test]1279 fn test_hl_inner_tail_exit_points() {1280 check(1281 r#"1282 fn foo() ->$0 u32 {1283//^^1284 if true {1285 unsafe {1286 return 5;1287 // ^^^^^^1288 51289 // ^1290 }1291 } else if false {1292 01293 // ^1294 } else {1295 match 5 {1296 6 => 100,1297 // ^^^1298 7 => loop {1299 break 5;1300 // ^^^^^1301 }1302 8 => 'a: loop {1303 'b: loop {1304 break 'a 5;1305 // ^^^^^1306 break 'b 5;1307 break 5;1308 };1309 }1310 //1311 _ => 500,1312 // ^^^1313 }1314 }1315}1316"#,1317 );1318 }13191320 #[test]1321 fn test_hl_inner_tail_exit_points_labeled_block() {1322 check(1323 r#"1324 fn foo() ->$0 u32 {1325//^^1326 'foo: {1327 break 'foo 0;1328 // ^^^^^1329 loop {1330 break;1331 break 'foo 0;1332 // ^^^^^1333 }1334 01335 // ^1336 }1337}1338"#,1339 );1340 }13411342 #[test]1343 fn test_hl_inner_tail_exit_points_loops() {1344 check(1345 r#"1346 fn foo() ->$0 u32 {1347//^^1348 'foo: while { return 0; true } {1349 // ^^^^^^1350 break 'foo 0;1351 // ^^^^^1352 return 0;1353 // ^^^^^^1354 }1355}1356"#,1357 );1358 }13591360 #[test]1361 fn test_hl_break_loop() {1362 check(1363 r#"1364fn foo() {1365 'outer: loop {1366 // ^^^^^^^^^^^^1367 break;1368 // ^^^^^1369 'inner: loop {1370 break;1371 'innermost: loop {1372 break 'outer;1373 // ^^^^^^^^^^^^1374 break 'inner;1375 }1376 break$0 'outer;1377 // ^^^^^^^^^^^^1378 break;1379 }1380 break;1381 // ^^^^^1382 }1383}1384"#,1385 );1386 }13871388 #[test]1389 fn test_hl_break_loop2() {1390 check(1391 r#"1392fn foo() {1393 'outer: loop {1394 break;1395 'inner: loop {1396 // ^^^^^^^^^^^^1397 break;1398 // ^^^^^1399 'innermost: loop {1400 break 'outer;1401 break 'inner;1402 // ^^^^^^^^^^^^1403 }1404 break 'outer;1405 break$0;1406 // ^^^^^1407 }1408 break;1409 }1410}1411"#,1412 );1413 }14141415 #[test]1416 fn test_hl_break_for() {1417 check(1418 r#"1419fn foo() {1420 'outer: for _ in () {1421 // ^^^^^^^^^^^1422 break;1423 // ^^^^^1424 'inner: for _ in () {1425 break;1426 'innermost: for _ in () {1427 break 'outer;1428 // ^^^^^^^^^^^^1429 break 'inner;1430 }1431 break$0 'outer;1432 // ^^^^^^^^^^^^1433 break;1434 }1435 break;1436 // ^^^^^1437 }1438}1439"#,1440 );1441 }14421443 #[test]1444 fn test_hl_break_for_but_not_continue() {1445 check(1446 r#"1447fn foo() {1448 'outer: for _ in () {1449 // ^^^^^^^^^^^1450 break;1451 // ^^^^^1452 continue;1453 'inner: for _ in () {1454 break;1455 continue;1456 'innermost: for _ in () {1457 continue 'outer;1458 break 'outer;1459 // ^^^^^^^^^^^^1460 continue 'inner;1461 break 'inner;1462 }1463 break$0 'outer;1464 // ^^^^^^^^^^^^1465 continue 'outer;1466 break;1467 continue;1468 }1469 break;1470 // ^^^^^1471 continue;1472 }1473}1474"#,1475 );1476 }14771478 #[test]1479 fn test_hl_continue_for_but_not_break() {1480 check(1481 r#"1482fn foo() {1483 'outer: for _ in () {1484 // ^^^^^^^^^^^1485 break;1486 continue;1487 // ^^^^^^^^1488 'inner: for _ in () {1489 break;1490 continue;1491 'innermost: for _ in () {1492 continue 'outer;1493 // ^^^^^^^^^^^^^^^1494 break 'outer;1495 continue 'inner;1496 break 'inner;1497 }1498 break 'outer;1499 continue$0 'outer;1500 // ^^^^^^^^^^^^^^^1501 break;1502 continue;1503 }1504 break;1505 continue;1506 // ^^^^^^^^1507 }1508}1509"#,1510 );1511 }15121513 #[test]1514 fn test_hl_break_and_continue() {1515 check(1516 r#"1517fn foo() {1518 'outer: fo$0r _ in () {1519 // ^^^^^^^^^^^1520 break;1521 // ^^^^^1522 continue;1523 // ^^^^^^^^1524 'inner: for _ in () {1525 break;1526 continue;1527 'innermost: for _ in () {1528 continue 'outer;1529 // ^^^^^^^^^^^^^^^1530 break 'outer;1531 // ^^^^^^^^^^^^1532 continue 'inner;1533 break 'inner;1534 }1535 break 'outer;1536 // ^^^^^^^^^^^^1537 continue 'outer;1538 // ^^^^^^^^^^^^^^^1539 break;1540 continue;1541 }1542 break;1543 // ^^^^^1544 continue;1545 // ^^^^^^^^1546 }1547}1548"#,1549 );1550 }15511552 #[test]1553 fn test_hl_break_while() {1554 check(1555 r#"1556fn foo() {1557 'outer: while true {1558 // ^^^^^^^^^^^^^1559 break;1560 // ^^^^^1561 'inner: while true {1562 break;1563 'innermost: while true {1564 break 'outer;1565 // ^^^^^^^^^^^^1566 break 'inner;1567 }1568 break$0 'outer;1569 // ^^^^^^^^^^^^1570 break;1571 }1572 break;1573 // ^^^^^1574 }1575}1576"#,1577 );1578 }15791580 #[test]1581 fn test_hl_break_labeled_block() {1582 check(1583 r#"1584fn foo() {1585 'outer: {1586 // ^^^^^^^1587 break;1588 // ^^^^^1589 'inner: {1590 break;1591 'innermost: {1592 break 'outer;1593 // ^^^^^^^^^^^^1594 break 'inner;1595 }1596 break$0 'outer;1597 // ^^^^^^^^^^^^1598 break;1599 }1600 break;1601 // ^^^^^1602 }1603}1604"#,1605 );1606 }16071608 #[test]1609 fn test_hl_break_unlabeled_loop() {1610 check(1611 r#"1612fn foo() {1613 loop {1614 // ^^^^1615 break$0;1616 // ^^^^^1617 }1618}1619"#,1620 );1621 }16221623 #[test]1624 fn test_hl_break_unlabeled_block_in_loop() {1625 check(1626 r#"1627fn foo() {1628 loop {1629 // ^^^^1630 {1631 break$0;1632 // ^^^^^1633 }1634 }1635}1636"#,1637 );1638 }16391640 #[test]1641 fn test_hl_field_shorthand() {1642 check(1643 r#"1644struct Struct { field: u32 }1645 //^^^^^1646fn function(field: u32) {1647 //^^^^^1648 Struct { field$0 }1649 //^^^^^ read1650}1651"#,1652 );1653 }16541655 #[test]1656 fn test_hl_disabled_ref_local() {1657 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16581659 check_with_config(1660 r#"1661fn foo() {1662 let x$0 = 5;1663 let y = x * 2;1664}1665"#,1666 config,1667 );1668 }16691670 #[test]1671 fn test_hl_disabled_ref_local_preserved_break() {1672 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };16731674 check_with_config(1675 r#"1676fn foo() {1677 let x$0 = 5;1678 let y = x * 2;16791680 loop {1681 break;1682 }1683}1684"#,1685 config.clone(),1686 );16871688 check_with_config(1689 r#"1690fn foo() {1691 let x = 5;1692 let y = x * 2;16931694 loop$0 {1695// ^^^^1696 break;1697// ^^^^^1698 }1699}1700"#,1701 config,1702 );1703 }17041705 #[test]1706 fn test_hl_disabled_ref_local_preserved_yield() {1707 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17081709 check_with_config(1710 r#"1711async fn foo() {1712 let x$0 = 5;1713 let y = x * 2;17141715 0.await;1716}1717"#,1718 config.clone(),1719 );17201721 check_with_config(1722 r#"1723 async fn foo() {1724// ^^^^^1725 let x = 5;1726 let y = x * 2;17271728 0.await$0;1729// ^^^^^1730}1731"#,1732 config,1733 );1734 }17351736 #[test]1737 fn test_hl_disabled_ref_local_preserved_exit() {1738 let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };17391740 check_with_config(1741 r#"1742fn foo() -> i32 {1743 let x$0 = 5;1744 let y = x * 2;17451746 if true {1747 return y;1748 }17491750 0?1751}1752"#,1753 config.clone(),1754 );17551756 check_with_config(1757 r#"1758 fn foo() ->$0 i32 {1759//^^1760 let x = 5;1761 let y = x * 2;17621763 if true {1764 return y;1765// ^^^^^^1766 }17671768 0?1769// ^1770"#,1771 config,1772 );1773 }17741775 #[test]1776 fn test_hl_disabled_break() {1777 let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };17781779 check_with_config(1780 r#"1781fn foo() {1782 loop {1783 break$0;1784 }1785}1786"#,1787 config,1788 );1789 }17901791 #[test]1792 fn test_hl_disabled_yield() {1793 let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };17941795 check_with_config(1796 r#"1797async$0 fn foo() {1798 0.await;1799}1800"#,1801 config,1802 );1803 }18041805 #[test]1806 fn test_hl_disabled_exit() {1807 let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };18081809 check_with_config(1810 r#"1811fn foo() ->$0 i32 {1812 if true {1813 return -1;1814 }18151816 421817}"#,1818 config,1819 );1820 }18211822 #[test]1823 fn test_hl_multi_local() {1824 check(1825 r#"1826fn foo((1827 foo$01828 //^^^1829 | foo1830 //^^^1831 | foo1832 //^^^1833): ()) {1834 foo;1835 //^^^read1836 let foo;1837}1838"#,1839 );1840 check(1841 r#"1842fn foo((1843 foo1844 //^^^1845 | foo$01846 //^^^1847 | foo1848 //^^^1849): ()) {1850 foo;1851 //^^^read1852 let foo;1853}1854"#,1855 );1856 check(1857 r#"1858fn foo((1859 foo1860 //^^^1861 | foo1862 //^^^1863 | foo1864 //^^^1865): ()) {1866 foo$0;1867 //^^^read1868 let foo;1869}1870"#,1871 );1872 }18731874 #[test]1875 fn test_hl_trait_impl_methods() {1876 check(1877 r#"1878trait Trait {1879 fn func$0(self) {}1880 //^^^^1881}18821883impl Trait for () {1884 fn func(self) {}1885 //^^^^1886}18871888fn main() {1889 <()>::func(());1890 //^^^^1891 ().func();1892 //^^^^1893}1894"#,1895 );1896 check(1897 r#"1898trait Trait {1899 fn func(self) {}1900}19011902impl Trait for () {1903 fn func$0(self) {}1904 //^^^^1905}19061907fn main() {1908 <()>::func(());1909 //^^^^1910 ().func();1911 //^^^^1912}1913"#,1914 );1915 check(1916 r#"1917trait Trait {1918 fn func(self) {}1919}19201921impl Trait for () {1922 fn func(self) {}1923 //^^^^1924}19251926fn main() {1927 <()>::func(());1928 //^^^^1929 ().func$0();1930 //^^^^1931}1932"#,1933 );1934 }19351936 #[test]1937 fn test_assoc_type_highlighting() {1938 check(1939 r#"1940trait Trait {1941 type Output;1942 // ^^^^^^1943}1944impl Trait for () {1945 type Output$0 = ();1946 // ^^^^^^1947}1948"#,1949 );1950 }19511952 #[test]1953 fn test_closure_capture_pipe() {1954 check(1955 r#"1956fn f() {1957 let x = 1;1958 // ^1959 let c = $0|y| x + y;1960 // ^ read1961}1962"#,1963 );1964 }19651966 #[test]1967 fn test_closure_capture_move() {1968 check(1969 r#"1970fn f() {1971 let x = 1;1972 // ^1973 let c = move$0 |y| x + y;1974 // ^ read1975}1976"#,1977 );1978 }19791980 #[test]1981 fn test_trait_highlights_assoc_item_uses() {1982 check(1983 r#"1984trait Super {1985 type SuperT;1986}1987trait Foo: Super {1988 //^^^1989 type T;1990 const C: usize;1991 fn f() {}1992 fn m(&self) {}1993}1994impl Foo for i32 {1995 //^^^1996 type T = i32;1997 const C: usize = 0;1998 fn f() {}1999 fn m(&self) {}2000}