From b26a8ecca142532d0be64ea16de233a92f4893f9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 22 Jun 2021 19:39:59 +0200 Subject: [PATCH 01/11] Move document highlighting computation from rust-analyzer to ide --- crates/ide/src/document_highlight.rs | 138 +++++++++++++++++++++++++++ crates/ide/src/lib.rs | 18 +++- crates/ide/src/references.rs | 52 ++++++++-- crates/rust-analyzer/src/handlers.rs | 45 ++++----- 4 files changed, 214 insertions(+), 39 deletions(-) create mode 100644 crates/ide/src/document_highlight.rs diff --git a/crates/ide/src/document_highlight.rs b/crates/ide/src/document_highlight.rs new file mode 100644 index 0000000000..ad202cf270 --- /dev/null +++ b/crates/ide/src/document_highlight.rs @@ -0,0 +1,138 @@ +use hir::Semantics; +use ide_db::{ + base_db::FilePosition, + defs::Definition, + search::{FileReference, ReferenceAccess, SearchScope}, + RootDatabase, +}; +use syntax::{AstNode, TextRange}; + +use crate::{display::TryToNav, references, NavigationTarget}; + +pub struct DocumentHighlight { + pub range: TextRange, + pub access: Option, +} + +// Feature: Document highlight +// +// Highlights the definition and its all references of the item at the cursor location in the current file. +pub(crate) fn document_highlight( + sema: &Semantics, + position: FilePosition, +) -> Option> { + let _p = profile::span("document_highlight"); + let syntax = sema.parse(position.file_id).syntax().clone(); + let def = references::find_def(sema, &syntax, position)?; + let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(position.file_id))).all(); + + let declaration = match def { + Definition::ModuleDef(hir::ModuleDef::Module(module)) => { + Some(NavigationTarget::from_module_to_decl(sema.db, module)) + } + def => def.try_to_nav(sema.db), + } + .filter(|decl| decl.file_id == position.file_id) + .and_then(|decl| { + let range = decl.focus_range?; + let access = references::decl_access(&def, &syntax, range); + Some(DocumentHighlight { range, access }) + }); + + let file_refs = usages.references.get(&position.file_id).map_or(&[][..], Vec::as_slice); + let mut res = Vec::with_capacity(file_refs.len() + 1); + res.extend(declaration); + res.extend( + file_refs + .iter() + .map(|&FileReference { access, range, .. }| DocumentHighlight { range, access }), + ); + Some(res) +} + +#[cfg(test)] +mod tests { + use crate::fixture; + + use super::*; + + fn check(ra_fixture: &str) { + let (analysis, pos, annotations) = fixture::annotations(ra_fixture); + let hls = analysis.highlight_document(pos).unwrap().unwrap(); + + let mut expected = annotations + .into_iter() + .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access))) + .collect::>(); + + let mut actual = hls + .into_iter() + .map(|hl| { + ( + hl.range, + hl.access.map(|it| { + match it { + ReferenceAccess::Read => "read", + ReferenceAccess::Write => "write", + } + .to_string() + }), + ) + }) + .collect::>(); + actual.sort_by_key(|(range, _)| range.start()); + expected.sort_by_key(|(range, _)| range.start()); + + assert_eq!(expected, actual); + } + + #[test] + fn test_hl_module() { + check( + r#" +//- /lib.rs +mod foo$0; + // ^^^ +//- /foo.rs +struct Foo; +"#, + ); + } + + #[test] + fn test_hl_self_in_crate_root() { + check( + r#" +//- /lib.rs +use self$0; +"#, + ); + } + + #[test] + fn test_hl_self_in_module() { + check( + r#" +//- /lib.rs +mod foo; +//- /foo.rs +use self$0; +"#, + ); + } + + #[test] + fn test_hl_local() { + check( + r#" +//- /lib.rs +fn foo() { + let mut bar = 3; + // ^^^ write + bar$0; + // ^^^ read +} +"#, + ); + } +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index ca14533f38..9661b8d4ad 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -24,32 +24,33 @@ mod display; mod annotations; mod call_hierarchy; +mod doc_links; +mod document_highlight; mod expand_macro; mod extend_selection; mod file_structure; +mod fn_references; mod folding_ranges; mod goto_definition; mod goto_implementation; mod goto_type_definition; -mod view_hir; mod hover; mod inlay_hints; mod join_lines; +mod markdown_remove; mod matching_brace; mod move_item; mod parent_module; mod references; mod rename; -mod fn_references; mod runnables; mod ssr; mod status; mod syntax_highlighting; mod syntax_tree; mod typing; -mod markdown_remove; -mod doc_links; mod view_crate_graph; +mod view_hir; mod view_item_tree; use std::sync::Arc; @@ -72,6 +73,7 @@ pub use crate::{ annotations::{Annotation, AnnotationConfig, AnnotationKind}, call_hierarchy::CallItem, display::navigation_target::NavigationTarget, + document_highlight::DocumentHighlight, expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, @@ -483,6 +485,14 @@ impl Analysis { self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) } + /// Computes all ranges to highlight for a given item in a file. + pub fn highlight_document( + &self, + position: FilePosition, + ) -> Cancellable>> { + self.with_db(|db| document_highlight::document_highlight(&Semantics::new(db), position)) + } + /// Computes syntax highlighting for the given file range. pub fn highlight_range(&self, frange: FileRange) -> Cancellable> { self.with_db(|db| { diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 5808562a77..4aed2b526b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -90,7 +90,13 @@ pub(crate) fn find_all_refs( _ => {} } } - let declaration = def.try_to_nav(sema.db).map(|nav| { + let declaration = match def { + Definition::ModuleDef(hir::ModuleDef::Module(module)) => { + Some(NavigationTarget::from_module_to_decl(sema.db, module)) + } + def => def.try_to_nav(sema.db), + } + .map(|nav| { let decl_range = nav.focus_or_full_range(); Declaration { nav, access: decl_access(&def, &syntax, decl_range) } }); @@ -104,7 +110,7 @@ pub(crate) fn find_all_refs( Some(ReferenceSearchResult { declaration, references }) } -fn find_def( +pub(crate) fn find_def( sema: &Semantics, syntax: &SyntaxNode, position: FilePosition, @@ -126,7 +132,11 @@ fn find_def( Some(def) } -fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option { +pub(crate) fn decl_access( + def: &Definition, + syntax: &SyntaxNode, + range: TextRange, +) -> Option { match def { Definition::Local(_) | Definition::Field(_) => {} _ => return None, @@ -658,9 +668,6 @@ fn f() { ); } - // `mod foo;` is not in the results because `foo` is an `ast::Name`. - // So, there are two references: the first one is a definition of the `foo` module, - // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. #[test] fn test_find_all_refs_decl_module() { check( @@ -680,13 +687,44 @@ pub struct Foo { } "#, expect![[r#" - foo Module FileId(1) 0..35 + foo Module FileId(0) 0..8 4..7 FileId(0) 14..17 "#]], ); } + #[test] + fn test_find_all_refs_decl_module_on_self() { + check( + r#" +//- /lib.rs +mod foo; + +//- /foo.rs +use self$0; +"#, + expect![[r#" + foo Module FileId(0) 0..8 4..7 + + "#]], + ); + } + + #[test] + fn test_find_all_refs_decl_module_on_self_crate_root() { + check( + r#" +//- /lib.rs +use self$0; +"#, + expect![[r#" + Module FileId(0) 0..10 + + "#]], + ); + } + #[test] fn test_find_all_refs_super_mod_vis() { check( diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index dcead5f5c3..d83ae25e3e 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -9,8 +9,8 @@ use std::{ use ide::{ AnnotationConfig, AssistKind, AssistResolveStrategy, FileId, FilePosition, FileRange, - HoverAction, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SearchScope, - SingleResolve, SourceChange, TextEdit, + HoverAction, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SingleResolve, + SourceChange, TextEdit, }; use ide_db::SymbolKind; use itertools::Itertools; @@ -18,12 +18,12 @@ use lsp_server::ErrorCode; use lsp_types::{ CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, - CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, - DocumentHighlight, FoldingRange, FoldingRangeParams, HoverContents, Location, NumberOrString, - Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams, - SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams, - SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag, - TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkspaceEdit, + CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange, + FoldingRangeParams, HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, + Range, RenameParams, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, + SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, + SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, + TextDocumentPositionParams, Url, WorkspaceEdit, }; use project_model::TargetKind; use serde::{Deserialize, Serialize}; @@ -1163,33 +1163,22 @@ pub(crate) fn handle_code_lens_resolve( pub(crate) fn handle_document_highlight( snap: GlobalStateSnapshot, params: lsp_types::DocumentHighlightParams, -) -> Result>> { +) -> Result>> { let _p = profile::span("handle_document_highlight"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; let line_index = snap.file_line_index(position.file_id)?; - let refs = match snap - .analysis - .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))? - { + let refs = match snap.analysis.highlight_document(position)? { None => return Ok(None), Some(refs) => refs, }; - - let decl = refs.declaration.filter(|decl| decl.nav.file_id == position.file_id).map(|decl| { - DocumentHighlight { - range: to_proto::range(&line_index, decl.nav.focus_or_full_range()), - kind: decl.access.map(to_proto::document_highlight_kind), - } - }); - - let file_refs = refs.references.get(&position.file_id).map_or(&[][..], Vec::as_slice); - let mut res = Vec::with_capacity(file_refs.len() + 1); - res.extend(decl); - res.extend(file_refs.iter().map(|&(range, access)| DocumentHighlight { - range: to_proto::range(&line_index, range), - kind: access.map(to_proto::document_highlight_kind), - })); + let res = refs + .into_iter() + .map(|ide::DocumentHighlight { range, access }| lsp_types::DocumentHighlight { + range: to_proto::range(&line_index, range), + kind: access.map(to_proto::document_highlight_kind), + }) + .collect(); Ok(Some(res)) } From 14b66bb4589569895d35742e995d1d1670b912cc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 14:56:40 +0200 Subject: [PATCH 02/11] Rename 'document_highlight' to 'highlight_related' --- ...t_highlight.rs => highlight_references.rs} | 52 +++++++++++++++---- crates/ide/src/lib.rs | 8 +-- crates/ide/src/references.rs | 8 +-- crates/rust-analyzer/src/handlers.rs | 2 +- 4 files changed, 51 insertions(+), 19 deletions(-) rename crates/ide/src/{document_highlight.rs => highlight_references.rs} (65%) diff --git a/crates/ide/src/document_highlight.rs b/crates/ide/src/highlight_references.rs similarity index 65% rename from crates/ide/src/document_highlight.rs rename to crates/ide/src/highlight_references.rs index ad202cf270..78e5a9f04e 100644 --- a/crates/ide/src/document_highlight.rs +++ b/crates/ide/src/highlight_references.rs @@ -2,10 +2,15 @@ use hir::Semantics; use ide_db::{ base_db::FilePosition, defs::Definition, + helpers::pick_best_token, search::{FileReference, ReferenceAccess, SearchScope}, RootDatabase, }; -use syntax::{AstNode, TextRange}; +use syntax::{ + AstNode, + SyntaxKind::{ASYNC_KW, AWAIT_KW, QUESTION, RETURN_KW, THIN_ARROW}, + SyntaxNode, TextRange, +}; use crate::{display::TryToNav, references, NavigationTarget}; @@ -14,17 +19,44 @@ pub struct DocumentHighlight { pub access: Option, } -// Feature: Document highlight +// Feature: Highlight related // -// Highlights the definition and its all references of the item at the cursor location in the current file. -pub(crate) fn document_highlight( +// Highlights exit points, yield points or the definition and all references of the item at the cursor location in the current file. +pub(crate) fn highlight_related( sema: &Semantics, position: FilePosition, ) -> Option> { let _p = profile::span("document_highlight"); let syntax = sema.parse(position.file_id).syntax().clone(); - let def = references::find_def(sema, &syntax, position)?; - let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(position.file_id))).all(); + + let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind { + QUESTION => 2, // prefer `?` when the cursor is sandwiched like `await$0?` + AWAIT_KW | ASYNC_KW | THIN_ARROW | RETURN_KW => 1, + _ => 0, + })?; + + match token.kind() { + QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(), + AWAIT_KW | ASYNC_KW => highlight_yield_points(), + _ => highlight_references(sema, &syntax, position), + } +} + +fn highlight_exit_points() -> Option> { + None +} + +fn highlight_yield_points() -> Option> { + None +} + +fn highlight_references( + sema: &Semantics, + syntax: &SyntaxNode, + FilePosition { offset, file_id }: FilePosition, +) -> Option> { + let def = references::find_def(sema, syntax, offset)?; + let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(file_id))).all(); let declaration = match def { Definition::ModuleDef(hir::ModuleDef::Module(module)) => { @@ -32,14 +64,14 @@ pub(crate) fn document_highlight( } def => def.try_to_nav(sema.db), } - .filter(|decl| decl.file_id == position.file_id) + .filter(|decl| decl.file_id == file_id) .and_then(|decl| { let range = decl.focus_range?; - let access = references::decl_access(&def, &syntax, range); + let access = references::decl_access(&def, syntax, range); Some(DocumentHighlight { range, access }) }); - let file_refs = usages.references.get(&position.file_id).map_or(&[][..], Vec::as_slice); + let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice); let mut res = Vec::with_capacity(file_refs.len() + 1); res.extend(declaration); res.extend( @@ -58,7 +90,7 @@ mod tests { fn check(ra_fixture: &str) { let (analysis, pos, annotations) = fixture::annotations(ra_fixture); - let hls = analysis.highlight_document(pos).unwrap().unwrap(); + let hls = analysis.highlight_related(pos).unwrap().unwrap(); let mut expected = annotations .into_iter() diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 9661b8d4ad..5e3420c046 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -25,7 +25,7 @@ mod display; mod annotations; mod call_hierarchy; mod doc_links; -mod document_highlight; +mod highlight_references; mod expand_macro; mod extend_selection; mod file_structure; @@ -73,10 +73,10 @@ pub use crate::{ annotations::{Annotation, AnnotationConfig, AnnotationKind}, call_hierarchy::CallItem, display::navigation_target::NavigationTarget, - document_highlight::DocumentHighlight, expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, + highlight_references::DocumentHighlight, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, markup::Markup, @@ -486,11 +486,11 @@ impl Analysis { } /// Computes all ranges to highlight for a given item in a file. - pub fn highlight_document( + pub fn highlight_related( &self, position: FilePosition, ) -> Cancellable>> { - self.with_db(|db| document_highlight::document_highlight(&Semantics::new(db), position)) + self.with_db(|db| highlight_references::highlight_related(&Semantics::new(db), position)) } /// Computes syntax highlighting for the given file range. diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 4aed2b526b..c0ede0b881 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -20,7 +20,7 @@ use rustc_hash::FxHashMap; use syntax::{ algo::find_node_at_offset, ast::{self, NameOwner}, - match_ast, AstNode, SyntaxNode, TextRange, T, + match_ast, AstNode, SyntaxNode, TextRange, TextSize, T, }; use crate::{display::TryToNav, FilePosition, NavigationTarget}; @@ -60,7 +60,7 @@ pub(crate) fn find_all_refs( if let Some(name) = get_name_of_item_declaration(&syntax, position) { (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) } else { - (find_def(sema, &syntax, position)?, false) + (find_def(sema, &syntax, position.offset)?, false) }; let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all(); @@ -113,9 +113,9 @@ pub(crate) fn find_all_refs( pub(crate) fn find_def( sema: &Semantics, syntax: &SyntaxNode, - position: FilePosition, + offset: TextSize, ) -> Option { - let def = match sema.find_node_at_offset_with_descend(syntax, position.offset)? { + let def = match sema.find_node_at_offset_with_descend(syntax, offset)? { ast::NameLike::NameRef(name_ref) => { NameRefClass::classify(sema, &name_ref)?.referenced(sema.db) } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index d83ae25e3e..66f07ec6c4 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1168,7 +1168,7 @@ pub(crate) fn handle_document_highlight( let position = from_proto::file_position(&snap, params.text_document_position_params)?; let line_index = snap.file_line_index(position.file_id)?; - let refs = match snap.analysis.highlight_document(position)? { + let refs = match snap.analysis.highlight_related(position)? { None => return Ok(None), Some(refs) => refs, }; From 9a53f1033efb166240d62cccf8f73501ada6938a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 16:16:32 +0200 Subject: [PATCH 03/11] Implement yield point highlighting --- ...ght_references.rs => highlight_related.rs} | 130 ++++++++++++++++-- crates/ide/src/lib.rs | 6 +- 2 files changed, 125 insertions(+), 11 deletions(-) rename crates/ide/src/{highlight_references.rs => highlight_related.rs} (56%) diff --git a/crates/ide/src/highlight_references.rs b/crates/ide/src/highlight_related.rs similarity index 56% rename from crates/ide/src/highlight_references.rs rename to crates/ide/src/highlight_related.rs index 78e5a9f04e..1daaeb43fa 100644 --- a/crates/ide/src/highlight_references.rs +++ b/crates/ide/src/highlight_related.rs @@ -7,9 +7,9 @@ use ide_db::{ RootDatabase, }; use syntax::{ - AstNode, + ast, match_ast, AstNode, SyntaxKind::{ASYNC_KW, AWAIT_KW, QUESTION, RETURN_KW, THIN_ARROW}, - SyntaxNode, TextRange, + SyntaxNode, SyntaxToken, TextRange, WalkEvent, }; use crate::{display::TryToNav, references, NavigationTarget}; @@ -36,17 +36,59 @@ pub(crate) fn highlight_related( })?; match token.kind() { - QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(), - AWAIT_KW | ASYNC_KW => highlight_yield_points(), + QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(token), + AWAIT_KW | ASYNC_KW => highlight_yield_points(token), _ => highlight_references(sema, &syntax, position), } } -fn highlight_exit_points() -> Option> { +fn highlight_exit_points(_token: SyntaxToken) -> Option> { None } -fn highlight_yield_points() -> Option> { +fn highlight_yield_points(token: SyntaxToken) -> Option> { + fn hl( + async_token: Option, + body: Option, + ) -> Option> { + let mut highlights = Vec::new(); + highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() }); + if let Some(body) = body { + let mut preorder = body.syntax().preorder(); + while let Some(event) = preorder.next() { + let node = match event { + WalkEvent::Enter(node) => node, + WalkEvent::Leave(_) => continue, + }; + match_ast! { + match node { + ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { + highlights.push(DocumentHighlight { + access: None, + range: token.text_range(), + }); + }, + ast::EffectExpr(__) => preorder.skip_subtree(), + ast::ClosureExpr(__) => preorder.skip_subtree(), + ast::Item(__) => preorder.skip_subtree(), + ast::Path(__) => preorder.skip_subtree(), + _ => (), + } + } + } + } + Some(highlights) + } + for anc in token.ancestors() { + return match_ast! { + match anc { + ast::Fn(fn_) => hl(fn_.async_token(), fn_.body()), + ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr()), + ast::ClosureExpr(__) => None, + _ => continue, + } + }; + } None } @@ -135,7 +177,6 @@ struct Foo; fn test_hl_self_in_crate_root() { check( r#" -//- /lib.rs use self$0; "#, ); @@ -157,13 +198,86 @@ use self$0; fn test_hl_local() { check( r#" -//- /lib.rs fn foo() { let mut bar = 3; // ^^^ write bar$0; // ^^^ read } +"#, + ); + } + + #[test] + fn test_hl_yield_points() { + check( + r#" +pub async fn foo() { + // ^^^^^ + let x = foo() + .await$0 + // ^^^^^ + .await; + // ^^^^^ + || { 0.await }; + (async { 0.await }).await + // ^^^^^ +} +"#, + ); + } + + #[test] + fn test_hl_yield_points2() { + check( + r#" +pub async$0 fn foo() { + // ^^^^^ + let x = foo() + .await + // ^^^^^ + .await; + // ^^^^^ + || { 0.await }; + (async { 0.await }).await + // ^^^^^ +} +"#, + ); + } + + #[test] + fn test_hl_yield_nested_fn() { + check( + r#" +async fn foo() { + async fn foo2() { + // ^^^^^ + async fn foo3() { + 0.await + } + 0.await$0 + // ^^^^^ + } + 0.await +} +"#, + ); + } + + #[test] + fn test_hl_yield_nested_async_blocks() { + check( + r#" +async fn foo() { + (async { + // ^^^^^ + (async { + 0.await + }).await$0 } + // ^^^^^ + ).await; +} "#, ); } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 5e3420c046..bb10ce5e8a 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -25,7 +25,7 @@ mod display; mod annotations; mod call_hierarchy; mod doc_links; -mod highlight_references; +mod highlight_related; mod expand_macro; mod extend_selection; mod file_structure; @@ -76,7 +76,7 @@ pub use crate::{ expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, - highlight_references::DocumentHighlight, + highlight_related::DocumentHighlight, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, markup::Markup, @@ -490,7 +490,7 @@ impl Analysis { &self, position: FilePosition, ) -> Cancellable>> { - self.with_db(|db| highlight_references::highlight_related(&Semantics::new(db), position)) + self.with_db(|db| highlight_related::highlight_related(&Semantics::new(db), position)) } /// Computes syntax highlighting for the given file range. From e406140f38dfc8917ac733b72bb9edd22f000490 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 16:43:53 +0200 Subject: [PATCH 04/11] Implement exit point highlighting --- crates/ide/src/highlight_related.rs | 220 +++++++++++++++++++++------- 1 file changed, 170 insertions(+), 50 deletions(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 1daaeb43fa..5493d11d07 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -42,56 +42,6 @@ pub(crate) fn highlight_related( } } -fn highlight_exit_points(_token: SyntaxToken) -> Option> { - None -} - -fn highlight_yield_points(token: SyntaxToken) -> Option> { - fn hl( - async_token: Option, - body: Option, - ) -> Option> { - let mut highlights = Vec::new(); - highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() }); - if let Some(body) = body { - let mut preorder = body.syntax().preorder(); - while let Some(event) = preorder.next() { - let node = match event { - WalkEvent::Enter(node) => node, - WalkEvent::Leave(_) => continue, - }; - match_ast! { - match node { - ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { - highlights.push(DocumentHighlight { - access: None, - range: token.text_range(), - }); - }, - ast::EffectExpr(__) => preorder.skip_subtree(), - ast::ClosureExpr(__) => preorder.skip_subtree(), - ast::Item(__) => preorder.skip_subtree(), - ast::Path(__) => preorder.skip_subtree(), - _ => (), - } - } - } - } - Some(highlights) - } - for anc in token.ancestors() { - return match_ast! { - match anc { - ast::Fn(fn_) => hl(fn_.async_token(), fn_.body()), - ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr()), - ast::ClosureExpr(__) => None, - _ => continue, - } - }; - } - None -} - fn highlight_references( sema: &Semantics, syntax: &SyntaxNode, @@ -124,6 +74,119 @@ fn highlight_references( Some(res) } +fn highlight_exit_points(token: SyntaxToken) -> Option> { + fn hl(body: Option) -> Option> { + let mut highlights = Vec::new(); + let body = body?; + walk(body.syntax(), |node| { + match_ast! { + match node { + ast::ReturnExpr(expr) => if let Some(token) = expr.return_token() { + highlights.push(DocumentHighlight { + access: None, + range: token.text_range(), + }); + }, + ast::TryExpr(try_) => if let Some(token) = try_.question_mark_token() { + highlights.push(DocumentHighlight { + access: None, + range: token.text_range(), + }); + }, + ast::EffectExpr(effect) => if effect.async_token().is_some() { + return true; + }, + ast::ClosureExpr(__) => return true, + ast::Item(__) => return true, + ast::Path(__) => return true, + _ => (), + } + } + false + }); + let tail = match body { + ast::Expr::BlockExpr(b) => b.tail_expr(), + e => Some(e), + }; + if let Some(tail) = tail { + highlights.push(DocumentHighlight { access: None, range: tail.syntax().text_range() }); + } + Some(highlights) + } + for anc in token.ancestors() { + return match_ast! { + match anc { + ast::Fn(fn_) => hl(fn_.body().map(ast::Expr::BlockExpr)), + ast::ClosureExpr(closure) => hl(closure.body()), + ast::EffectExpr(effect) => if effect.async_token().is_some() { + None + } else { + continue; + }, + _ => continue, + } + }; + } + None +} + +fn highlight_yield_points(token: SyntaxToken) -> Option> { + fn hl( + async_token: Option, + body: Option, + ) -> Option> { + let mut highlights = Vec::new(); + highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() }); + if let Some(body) = body { + walk(body.syntax(), |node| { + match_ast! { + match node { + ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { + highlights.push(DocumentHighlight { + access: None, + range: token.text_range(), + }); + }, + ast::EffectExpr(effect) => if effect.async_token().is_some() { + return true; + }, + ast::ClosureExpr(__) => return true, + ast::Item(__) => return true, + ast::Path(__) => return true, + _ => (), + } + } + false + }); + } + Some(highlights) + } + for anc in token.ancestors() { + return match_ast! { + match anc { + ast::Fn(fn_) => hl(fn_.async_token(), fn_.body()), + ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr()), + ast::ClosureExpr(__) => None, + _ => continue, + } + }; + } + None +} + +fn walk(syntax: &SyntaxNode, mut cb: impl FnMut(SyntaxNode) -> bool) { + let mut preorder = syntax.preorder(); + while let Some(event) = preorder.next() { + let node = match event { + WalkEvent::Enter(node) => node, + WalkEvent::Leave(_) => continue, + }; + if cb(node) { + preorder.skip_subtree(); + } + } +} + #[cfg(test)] mod tests { use crate::fixture; @@ -278,6 +341,63 @@ async fn foo() { // ^^^^^ ).await; } +"#, + ); + } + + #[test] + fn test_hl_exit_points() { + check( + r#" +fn foo() -> u32 { + if true { + return$0 0; + // ^^^^^^ + } + + 0?; + // ^ + 0xDEAD_BEEF + // ^^^^^^^^^^^ +} +"#, + ); + } + + #[test] + fn test_hl_exit_points2() { + check( + r#" +fn foo() ->$0 u32 { + if true { + return 0; + // ^^^^^^ + } + + 0?; + // ^ + 0xDEAD_BEEF + // ^^^^^^^^^^^ +} +"#, + ); + } + + #[test] + fn test_hl_prefer_ref_over_tail_exit() { + check( + r#" +fn foo() -> u32 { +// ^^^ + if true { + return 0; + } + + 0?; + + foo$0() + // ^^^ +} "#, ); } From 12266d5e56c80a06dbfb4a78c070b57ac96ac7f5 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 16:49:36 +0200 Subject: [PATCH 05/11] Strip leading whitespace from test ouput in `references` --- crates/ide/src/references.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index c0ede0b881..2d3a0f5981 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1059,7 +1059,7 @@ impl Foo { actual += "\n"; } } - expect.assert_eq(&actual) + expect.assert_eq(actual.trim_start()) } #[test] From cc791538d6f6b5cb09010c93b0b645009953dc18 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 17:05:00 +0200 Subject: [PATCH 06/11] Simplify --- crates/ide/src/highlight_related.rs | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 5493d11d07..48777223aa 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -19,7 +19,7 @@ pub struct DocumentHighlight { pub access: Option, } -// Feature: Highlight related +// Feature: Highlight Related // // Highlights exit points, yield points or the definition and all references of the item at the cursor location in the current file. pub(crate) fn highlight_related( @@ -78,7 +78,7 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { fn hl(body: Option) -> Option> { let mut highlights = Vec::new(); let body = body?; - walk(body.syntax(), |node| { + walk(&body, |node| { match_ast! { match node { ast::ReturnExpr(expr) => if let Some(token) = expr.return_token() { @@ -93,11 +93,11 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { range: token.text_range(), }); }, - ast::EffectExpr(effect) => if effect.async_token().is_some() { - return true; - }, + // All the following are different contexts so skip them + ast::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), ast::ClosureExpr(__) => return true, ast::Item(__) => return true, + // Don't look into const args ast::Path(__) => return true, _ => (), } @@ -118,8 +118,8 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { match anc { ast::Fn(fn_) => hl(fn_.body().map(ast::Expr::BlockExpr)), ast::ClosureExpr(closure) => hl(closure.body()), - ast::EffectExpr(effect) => if effect.async_token().is_some() { - None + ast::EffectExpr(effect) => if effect.async_token().is_some() || effect.try_token().is_some() { + hl(effect.block_expr().map(ast::Expr::BlockExpr)) } else { continue; }, @@ -133,12 +133,12 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { fn highlight_yield_points(token: SyntaxToken) -> Option> { fn hl( async_token: Option, - body: Option, + body: Option, ) -> Option> { let mut highlights = Vec::new(); highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() }); if let Some(body) = body { - walk(body.syntax(), |node| { + walk(&body, |node| { match_ast! { match node { ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { @@ -147,11 +147,11 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> range: token.text_range(), }); }, - ast::EffectExpr(effect) => if effect.async_token().is_some() { - return true; - }, + // All the following are different contexts so skip them + ast::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), ast::ClosureExpr(__) => return true, ast::Item(__) => return true, + // Don't look into const args ast::Path(__) => return true, _ => (), } @@ -164,9 +164,9 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> for anc in token.ancestors() { return match_ast! { match anc { - ast::Fn(fn_) => hl(fn_.async_token(), fn_.body()), - ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr()), - ast::ClosureExpr(__) => None, + ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), + ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)), + ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()), _ => continue, } }; @@ -174,8 +174,9 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> None } -fn walk(syntax: &SyntaxNode, mut cb: impl FnMut(SyntaxNode) -> bool) { - let mut preorder = syntax.preorder(); +/// Preorder walk the expression node skipping a node's subtrees if the callback returns `true` for the node. +fn walk(expr: &ast::Expr, mut cb: impl FnMut(SyntaxNode) -> bool) { + let mut preorder = expr.syntax().preorder(); while let Some(event) = preorder.next() { let node = match event { WalkEvent::Enter(node) => node, From f283fce59467d20c0bc7247592b27015d0f52e73 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 17:21:47 +0200 Subject: [PATCH 07/11] Mark (method-)calls with never type as exit points --- crates/hir/src/lib.rs | 5 +++ crates/ide/src/highlight_related.rs | 62 ++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 3f00008897..3a901cbbf8 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2075,10 +2075,15 @@ impl Type { pub fn is_unit(&self) -> bool { matches!(self.ty.kind(&Interner), TyKind::Tuple(0, ..)) } + pub fn is_bool(&self) -> bool { matches!(self.ty.kind(&Interner), TyKind::Scalar(Scalar::Bool)) } + pub fn is_never(&self) -> bool { + matches!(self.ty.kind(&Interner), TyKind::Never) + } + pub fn is_mutable_reference(&self) -> bool { matches!(self.ty.kind(&Interner), TyKind::Ref(hir_ty::Mutability::Mut, ..)) } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 48777223aa..de918ac86f 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -36,7 +36,7 @@ pub(crate) fn highlight_related( })?; match token.kind() { - QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(token), + QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(sema, token), AWAIT_KW | ASYNC_KW => highlight_yield_points(token), _ => highlight_references(sema, &syntax, position), } @@ -74,8 +74,14 @@ fn highlight_references( Some(res) } -fn highlight_exit_points(token: SyntaxToken) -> Option> { - fn hl(body: Option) -> Option> { +fn highlight_exit_points( + sema: &Semantics, + token: SyntaxToken, +) -> Option> { + fn hl( + sema: &Semantics, + body: Option, + ) -> Option> { let mut highlights = Vec::new(); let body = body?; walk(&body, |node| { @@ -93,9 +99,19 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { range: token.text_range(), }); }, - // All the following are different contexts so skip them - ast::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), - ast::ClosureExpr(__) => return true, + ast::Expr(expr) => match expr { + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { + if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { + highlights.push(DocumentHighlight { + access: None, + range: expr.syntax().text_range(), + }); + } + }, + ast::Expr::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), + ast::Expr::ClosureExpr(_) => return true, + _ => (), + }, ast::Item(__) => return true, // Don't look into const args ast::Path(__) => return true, @@ -116,10 +132,10 @@ fn highlight_exit_points(token: SyntaxToken) -> Option> { for anc in token.ancestors() { return match_ast! { match anc { - ast::Fn(fn_) => hl(fn_.body().map(ast::Expr::BlockExpr)), - ast::ClosureExpr(closure) => hl(closure.body()), + ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)), + ast::ClosureExpr(closure) => hl(sema, closure.body()), ast::EffectExpr(effect) => if effect.async_token().is_some() || effect.try_token().is_some() { - hl(effect.block_expr().map(ast::Expr::BlockExpr)) + hl(sema, effect.block_expr().map(ast::Expr::BlockExpr)) } else { continue; }, @@ -399,6 +415,34 @@ fn foo() -> u32 { foo$0() // ^^^ } +"#, + ); + } + + #[test] + fn test_hl_never_call_is_exit_point() { + check( + r#" +struct Never; +impl Never { + fn never(self) -> ! { loop {} } +} +macro_rules! never { + () => { never() } +} +fn never() -> ! { loop {} } +fn foo() ->$0 u32 { + never(); + // ^^^^^^^ + never!(); + // FIXME sema doesnt give us types for macrocalls + + Never.never(); + // ^^^^^^^^^^^^^ + + 0 + // ^ +} "#, ); } From 511ae17d07f3be1db0f1d6c43ee4fd51eae881f9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 23 Jun 2021 18:11:48 +0200 Subject: [PATCH 08/11] Improve feature docs for highlight_related --- crates/ide/src/highlight_related.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index de918ac86f..d2c6c3c9dc 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -21,7 +21,10 @@ pub struct DocumentHighlight { // Feature: Highlight Related // -// Highlights exit points, yield points or the definition and all references of the item at the cursor location in the current file. +// Highlights constructs related to the thing under the cursor: +// - if on an identifier, highlights all references to that identifier in the current file +// - if on an `async` or `await token, highlights all yield points for that async context +// - if on a `return` token, `?` character or `->` return type arrow, highlights all exit points for that context pub(crate) fn highlight_related( sema: &Semantics, position: FilePosition, From 066bc4f3a467ddf45954d71e970aa6c776fb9ea4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 24 Jun 2021 01:32:56 +0200 Subject: [PATCH 09/11] Simplify --- crates/ide/src/highlight_related.rs | 46 +++++++++++++--------------- crates/ide/src/lib.rs | 4 +-- crates/rust-analyzer/src/handlers.rs | 2 +- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index d2c6c3c9dc..c0a4080ded 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -6,15 +6,11 @@ use ide_db::{ search::{FileReference, ReferenceAccess, SearchScope}, RootDatabase, }; -use syntax::{ - ast, match_ast, AstNode, - SyntaxKind::{ASYNC_KW, AWAIT_KW, QUESTION, RETURN_KW, THIN_ARROW}, - SyntaxNode, SyntaxToken, TextRange, WalkEvent, -}; +use syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, WalkEvent, T}; use crate::{display::TryToNav, references, NavigationTarget}; -pub struct DocumentHighlight { +pub struct HighlightedRange { pub range: TextRange, pub access: Option, } @@ -28,19 +24,19 @@ pub struct DocumentHighlight { pub(crate) fn highlight_related( sema: &Semantics, position: FilePosition, -) -> Option> { - let _p = profile::span("document_highlight"); +) -> Option> { + let _p = profile::span("highlight_related"); let syntax = sema.parse(position.file_id).syntax().clone(); let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind { - QUESTION => 2, // prefer `?` when the cursor is sandwiched like `await$0?` - AWAIT_KW | ASYNC_KW | THIN_ARROW | RETURN_KW => 1, + T![?] => 2, // prefer `?` when the cursor is sandwiched like `await$0?` + T![await] | T![async] | T![->] | T![return] => 1, _ => 0, })?; match token.kind() { - QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(sema, token), - AWAIT_KW | ASYNC_KW => highlight_yield_points(token), + T![?] | T![return] | T![->] => highlight_exit_points(sema, token), + T![await] | T![async] => highlight_yield_points(token), _ => highlight_references(sema, &syntax, position), } } @@ -49,7 +45,7 @@ fn highlight_references( sema: &Semantics, syntax: &SyntaxNode, FilePosition { offset, file_id }: FilePosition, -) -> Option> { +) -> Option> { let def = references::find_def(sema, syntax, offset)?; let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(file_id))).all(); @@ -63,7 +59,7 @@ fn highlight_references( .and_then(|decl| { let range = decl.focus_range?; let access = references::decl_access(&def, syntax, range); - Some(DocumentHighlight { range, access }) + Some(HighlightedRange { range, access }) }); let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice); @@ -72,7 +68,7 @@ fn highlight_references( res.extend( file_refs .iter() - .map(|&FileReference { access, range, .. }| DocumentHighlight { range, access }), + .map(|&FileReference { access, range, .. }| HighlightedRange { range, access }), ); Some(res) } @@ -80,24 +76,24 @@ fn highlight_references( fn highlight_exit_points( sema: &Semantics, token: SyntaxToken, -) -> Option> { +) -> Option> { fn hl( sema: &Semantics, body: Option, - ) -> Option> { + ) -> Option> { let mut highlights = Vec::new(); let body = body?; walk(&body, |node| { match_ast! { match node { ast::ReturnExpr(expr) => if let Some(token) = expr.return_token() { - highlights.push(DocumentHighlight { + highlights.push(HighlightedRange { access: None, range: token.text_range(), }); }, ast::TryExpr(try_) => if let Some(token) = try_.question_mark_token() { - highlights.push(DocumentHighlight { + highlights.push(HighlightedRange { access: None, range: token.text_range(), }); @@ -105,7 +101,7 @@ fn highlight_exit_points( ast::Expr(expr) => match expr { ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { - highlights.push(DocumentHighlight { + highlights.push(HighlightedRange { access: None, range: expr.syntax().text_range(), }); @@ -128,7 +124,7 @@ fn highlight_exit_points( e => Some(e), }; if let Some(tail) = tail { - highlights.push(DocumentHighlight { access: None, range: tail.syntax().text_range() }); + highlights.push(HighlightedRange { access: None, range: tail.syntax().text_range() }); } Some(highlights) } @@ -149,19 +145,19 @@ fn highlight_exit_points( None } -fn highlight_yield_points(token: SyntaxToken) -> Option> { +fn highlight_yield_points(token: SyntaxToken) -> Option> { fn hl( async_token: Option, body: Option, - ) -> Option> { + ) -> Option> { let mut highlights = Vec::new(); - highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() }); + highlights.push(HighlightedRange { access: None, range: async_token?.text_range() }); if let Some(body) = body { walk(&body, |node| { match_ast! { match node { ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { - highlights.push(DocumentHighlight { + highlights.push(HighlightedRange { access: None, range: token.text_range(), }); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index bb10ce5e8a..dc1448f636 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -76,7 +76,7 @@ pub use crate::{ expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, - highlight_related::DocumentHighlight, + highlight_related::HighlightedRange, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, markup::Markup, @@ -489,7 +489,7 @@ impl Analysis { pub fn highlight_related( &self, position: FilePosition, - ) -> Cancellable>> { + ) -> Cancellable>> { self.with_db(|db| highlight_related::highlight_related(&Semantics::new(db), position)) } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 66f07ec6c4..3e6ab70e46 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1174,7 +1174,7 @@ pub(crate) fn handle_document_highlight( }; let res = refs .into_iter() - .map(|ide::DocumentHighlight { range, access }| lsp_types::DocumentHighlight { + .map(|ide::HighlightedRange { range, access }| lsp_types::DocumentHighlight { range: to_proto::range(&line_index, range), kind: access.map(to_proto::document_highlight_kind), }) From d049783b5d854ceea1dbdd2bd6ce129a95202c80 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 24 Jun 2021 16:50:56 +0200 Subject: [PATCH 10/11] Simplify --- crates/ide/src/highlight_related.rs | 123 +++++++++++++++------------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index c0a4080ded..d28a80ac09 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -30,12 +30,12 @@ pub(crate) fn highlight_related( let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind { T![?] => 2, // prefer `?` when the cursor is sandwiched like `await$0?` - T![await] | T![async] | T![->] | T![return] => 1, + T![await] | T![async] | T![return] | T![->] => 1, _ => 0, })?; match token.kind() { - T![?] | T![return] | T![->] => highlight_exit_points(sema, token), + T![return] | T![?] | T![->] => highlight_exit_points(sema, token), T![await] | T![async] => highlight_yield_points(token), _ => highlight_references(sema, &syntax, position), } @@ -83,39 +83,33 @@ fn highlight_exit_points( ) -> Option> { let mut highlights = Vec::new(); let body = body?; - walk(&body, |node| { - match_ast! { - match node { - ast::ReturnExpr(expr) => if let Some(token) = expr.return_token() { - highlights.push(HighlightedRange { - access: None, - range: token.text_range(), - }); - }, - ast::TryExpr(try_) => if let Some(token) = try_.question_mark_token() { - highlights.push(HighlightedRange { - access: None, - range: token.text_range(), - }); - }, - ast::Expr(expr) => match expr { - ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { - if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { - highlights.push(HighlightedRange { - access: None, - range: expr.syntax().text_range(), - }); - } - }, - ast::Expr::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), - ast::Expr::ClosureExpr(_) => return true, - _ => (), - }, - ast::Item(__) => return true, - // Don't look into const args - ast::Path(__) => return true, - _ => (), + walk(&body, &mut |expr| { + match expr { + ast::Expr::ReturnExpr(expr) => { + if let Some(token) = expr.return_token() { + highlights + .push(HighlightedRange { access: None, range: token.text_range() }); + } } + ast::Expr::TryExpr(try_) => { + if let Some(token) = try_.question_mark_token() { + highlights + .push(HighlightedRange { access: None, range: token.text_range() }); + } + } + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { + if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { + highlights.push(HighlightedRange { + access: None, + range: expr.syntax().text_range(), + }); + } + } + ast::Expr::EffectExpr(effect) => { + return effect.async_token().is_some() || effect.try_token().is_some() + } + ast::Expr::ClosureExpr(_) => return true, + _ => (), } false }); @@ -123,8 +117,9 @@ fn highlight_exit_points( ast::Expr::BlockExpr(b) => b.tail_expr(), e => Some(e), }; + if let Some(tail) = tail { - highlights.push(HighlightedRange { access: None, range: tail.syntax().text_range() }); + highlights.push(HighlightedRange { access: None, range: tail.syntax().text_range() }) } Some(highlights) } @@ -133,7 +128,7 @@ fn highlight_exit_points( match anc { ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)), ast::ClosureExpr(closure) => hl(sema, closure.body()), - ast::EffectExpr(effect) => if effect.async_token().is_some() || effect.try_token().is_some() { + ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) { hl(sema, effect.block_expr().map(ast::Expr::BlockExpr)) } else { continue; @@ -153,23 +148,23 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { let mut highlights = Vec::new(); highlights.push(HighlightedRange { access: None, range: async_token?.text_range() }); if let Some(body) = body { - walk(&body, |node| { - match_ast! { - match node { - ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() { - highlights.push(HighlightedRange { - access: None, - range: token.text_range(), - }); - }, - // All the following are different contexts so skip them - ast::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(), - ast::ClosureExpr(__) => return true, - ast::Item(__) => return true, - // Don't look into const args - ast::Path(__) => return true, - _ => (), + walk(&body, &mut |expr| { + match expr { + ast::Expr::AwaitExpr(expr) => { + if let Some(token) = expr.await_token() { + highlights + .push(HighlightedRange { access: None, range: token.text_range() }); + } } + // All the following are different contexts so skip them + ast::Expr::EffectExpr(effect) => { + return matches!( + effect.effect(), + ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) + ) + } + ast::Expr::ClosureExpr(__) => return true, + _ => (), } false }); @@ -190,16 +185,32 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { } /// Preorder walk the expression node skipping a node's subtrees if the callback returns `true` for the node. -fn walk(expr: &ast::Expr, mut cb: impl FnMut(SyntaxNode) -> bool) { +fn walk(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr) -> bool) { let mut preorder = expr.syntax().preorder(); while let Some(event) = preorder.next() { let node = match event { WalkEvent::Enter(node) => node, WalkEvent::Leave(_) => continue, }; - if cb(node) { - preorder.skip_subtree(); + match ast::Stmt::cast(node.clone()) { + Some(ast::Stmt::LetStmt(l)) => { + if let Some(expr) = l.initializer() { + walk(&expr, cb); + } + } + // Don't skip subtree since we want to process the expression behind this next + Some(ast::Stmt::ExprStmt(_)) => continue, + // skip inner items which might have their own expressions + Some(ast::Stmt::Item(_)) => (), + None => { + if let Some(expr) = ast::Expr::cast(node) { + if !cb(expr) { + continue; + } + } + } } + preorder.skip_subtree(); } } @@ -434,7 +445,7 @@ fn foo() ->$0 u32 { never(); // ^^^^^^^ never!(); - // FIXME sema doesnt give us types for macrocalls + // FIXME sema doesn't give us types for macrocalls Never.never(); // ^^^^^^^^^^^^^ From d77655e5c3c6c09bc968715b2b7a3db6224f7883 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 24 Jun 2021 17:19:27 +0200 Subject: [PATCH 11/11] Refine tail exit point highlighting to highlight inner tails --- crates/ide/src/highlight_related.rs | 233 +++++++++++++++++++++------- 1 file changed, 180 insertions(+), 53 deletions(-) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index d28a80ac09..1c17d35110 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -6,7 +6,10 @@ use ide_db::{ search::{FileReference, ReferenceAccess, SearchScope}, RootDatabase, }; -use syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, WalkEvent, T}; +use syntax::{ + ast::{self, LoopBodyOwner}, + match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, +}; use crate::{display::TryToNav, references, NavigationTarget}; @@ -83,35 +86,24 @@ fn highlight_exit_points( ) -> Option> { let mut highlights = Vec::new(); let body = body?; - walk(&body, &mut |expr| { - match expr { - ast::Expr::ReturnExpr(expr) => { - if let Some(token) = expr.return_token() { - highlights - .push(HighlightedRange { access: None, range: token.text_range() }); - } + walk(&body, &mut |expr| match expr { + ast::Expr::ReturnExpr(expr) => { + if let Some(token) = expr.return_token() { + highlights.push(HighlightedRange { access: None, range: token.text_range() }); } - ast::Expr::TryExpr(try_) => { - if let Some(token) = try_.question_mark_token() { - highlights - .push(HighlightedRange { access: None, range: token.text_range() }); - } - } - ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { - if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { - highlights.push(HighlightedRange { - access: None, - range: expr.syntax().text_range(), - }); - } - } - ast::Expr::EffectExpr(effect) => { - return effect.async_token().is_some() || effect.try_token().is_some() - } - ast::Expr::ClosureExpr(_) => return true, - _ => (), } - false + ast::Expr::TryExpr(try_) => { + if let Some(token) = try_.question_mark_token() { + highlights.push(HighlightedRange { access: None, range: token.text_range() }); + } + } + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => { + if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) { + highlights + .push(HighlightedRange { access: None, range: expr.syntax().text_range() }); + } + } + _ => (), }); let tail = match body { ast::Expr::BlockExpr(b) => b.tail_expr(), @@ -119,7 +111,10 @@ fn highlight_exit_points( }; if let Some(tail) = tail { - highlights.push(HighlightedRange { access: None, range: tail.syntax().text_range() }) + for_each_inner_tail(&tail, &mut |tail| { + highlights + .push(HighlightedRange { access: None, range: tail.syntax().text_range() }) + }); } Some(highlights) } @@ -149,24 +144,12 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { highlights.push(HighlightedRange { access: None, range: async_token?.text_range() }); if let Some(body) = body { walk(&body, &mut |expr| { - match expr { - ast::Expr::AwaitExpr(expr) => { - if let Some(token) = expr.await_token() { - highlights - .push(HighlightedRange { access: None, range: token.text_range() }); - } + if let ast::Expr::AwaitExpr(expr) = expr { + if let Some(token) = expr.await_token() { + highlights + .push(HighlightedRange { access: None, range: token.text_range() }); } - // All the following are different contexts so skip them - ast::Expr::EffectExpr(effect) => { - return matches!( - effect.effect(), - ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) - ) - } - ast::Expr::ClosureExpr(__) => return true, - _ => (), } - false }); } Some(highlights) @@ -184,8 +167,8 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { None } -/// Preorder walk the expression node skipping a node's subtrees if the callback returns `true` for the node. -fn walk(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr) -> bool) { +/// Preorder walk all the expression's child expressions +fn walk(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) { let mut preorder = expr.syntax().preorder(); while let Some(event) = preorder.next() { let node = match event { @@ -193,24 +176,130 @@ fn walk(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr) -> bool) { WalkEvent::Leave(_) => continue, }; match ast::Stmt::cast(node.clone()) { + // recursively walk the initializer, skipping potential const pat expressions + // lets statements aren't usually nested too deeply so this is fine to recurse on Some(ast::Stmt::LetStmt(l)) => { if let Some(expr) = l.initializer() { walk(&expr, cb); } + preorder.skip_subtree(); } - // Don't skip subtree since we want to process the expression behind this next - Some(ast::Stmt::ExprStmt(_)) => continue, + // Don't skip subtree since we want to process the expression child next + Some(ast::Stmt::ExprStmt(_)) => (), // skip inner items which might have their own expressions - Some(ast::Stmt::Item(_)) => (), + Some(ast::Stmt::Item(_)) => preorder.skip_subtree(), None => { if let Some(expr) = ast::Expr::cast(node) { - if !cb(expr) { - continue; + let is_different_context = match &expr { + ast::Expr::EffectExpr(effect) => { + matches!( + effect.effect(), + ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) + ) + } + ast::Expr::ClosureExpr(__) => true, + _ => false, + }; + cb(expr); + if is_different_context { + preorder.skip_subtree(); } + } else { + preorder.skip_subtree(); } } } - preorder.skip_subtree(); + } +} + +// FIXME: doesn't account for labeled breaks in labeled blocks +fn for_each_inner_tail(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { + match expr { + ast::Expr::BlockExpr(b) => { + if let Some(e) = b.tail_expr() { + for_each_inner_tail(&e, cb); + } + } + ast::Expr::EffectExpr(e) => match e.effect() { + ast::Effect::Label(_) | ast::Effect::Unsafe(_) => { + if let Some(e) = e.block_expr().and_then(|b| b.tail_expr()) { + for_each_inner_tail(&e, cb); + } + } + ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) => cb(expr), + }, + ast::Expr::IfExpr(if_) => { + if_.blocks().for_each(|block| for_each_inner_tail(&ast::Expr::BlockExpr(block), cb)) + } + ast::Expr::LoopExpr(l) => for_each_break(l, cb), + ast::Expr::MatchExpr(m) => { + if let Some(arms) = m.match_arm_list() { + arms.arms().filter_map(|arm| arm.expr()).for_each(|e| for_each_inner_tail(&e, cb)); + } + } + ast::Expr::ArrayExpr(_) + | ast::Expr::AwaitExpr(_) + | ast::Expr::BinExpr(_) + | ast::Expr::BoxExpr(_) + | ast::Expr::BreakExpr(_) + | ast::Expr::CallExpr(_) + | ast::Expr::CastExpr(_) + | ast::Expr::ClosureExpr(_) + | ast::Expr::ContinueExpr(_) + | ast::Expr::FieldExpr(_) + | ast::Expr::ForExpr(_) + | ast::Expr::IndexExpr(_) + | ast::Expr::Literal(_) + | ast::Expr::MacroCall(_) + | ast::Expr::MacroStmts(_) + | ast::Expr::MethodCallExpr(_) + | ast::Expr::ParenExpr(_) + | ast::Expr::PathExpr(_) + | ast::Expr::PrefixExpr(_) + | ast::Expr::RangeExpr(_) + | ast::Expr::RecordExpr(_) + | ast::Expr::RefExpr(_) + | ast::Expr::ReturnExpr(_) + | ast::Expr::TryExpr(_) + | ast::Expr::TupleExpr(_) + | ast::Expr::WhileExpr(_) + | ast::Expr::YieldExpr(_) => cb(expr), + } +} + +fn for_each_break(l: &ast::LoopExpr, cb: &mut dyn FnMut(&ast::Expr)) { + let label = l.label().and_then(|lbl| lbl.lifetime()); + let mut depth = 0; + if let Some(b) = l.loop_body() { + let preorder = &mut b.syntax().preorder(); + let ev_as_expr = |ev| match ev { + WalkEvent::Enter(it) => Some(WalkEvent::Enter(ast::Expr::cast(it)?)), + WalkEvent::Leave(it) => Some(WalkEvent::Leave(ast::Expr::cast(it)?)), + }; + let eq_label = |lt: Option| { + lt.zip(label.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text()) + }; + while let Some(node) = preorder.find_map(ev_as_expr) { + match node { + WalkEvent::Enter(expr) => match &expr { + ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => { + depth += 1 + } + ast::Expr::EffectExpr(e) if e.label().is_some() => depth += 1, + ast::Expr::BreakExpr(b) if depth == 0 || eq_label(b.lifetime()) => { + cb(&expr); + } + _ => (), + }, + WalkEvent::Leave(expr) => match expr { + ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => { + depth -= 1 + } + ast::Expr::EffectExpr(e) if e.label().is_some() => depth -= 1, + _ => (), + }, + } + } } } @@ -453,6 +542,44 @@ fn foo() ->$0 u32 { 0 // ^ } +"#, + ); + } + + #[test] + fn test_hl_inner_tail_exit_points() { + check( + r#" +fn foo() ->$0 u32 { + if true { + unsafe { + return 5; + // ^^^^^^ + 5 + // ^ + } + } else { + match 5 { + 6 => 100, + // ^^^ + 7 => loop { + break 5; + // ^^^^^^^ + } + 8 => 'a: loop { + 'b: loop { + break 'a 5; + // ^^^^^^^^^^ + break 'b 5; + break 5; + }; + } + // + _ => 500, + // ^^^ + } + } +} "#, ); }