From a167dd0ffb70823454e91e4d79e624f29e45154d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 15:42:53 +0100 Subject: [PATCH 1/6] Support labels in reference search --- crates/ide/src/references.rs | 149 ++++++++++++++++++++++++++++++++++- crates/ide_db/src/search.rs | 1 + 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 7395b81bd291..4890185e21e3 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -20,8 +20,8 @@ use ide_db::{ }; use syntax::{ algo::find_node_at_offset, - ast::{self, NameOwner}, - match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset, + ast::{self, LoopBodyOwner, NameOwner}, + match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TokenAtOffset, }; use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; @@ -93,6 +93,10 @@ pub(crate) fn find_all_refs( return Some(res); } + if let Some(res) = try_find_lifetime_references(&syntax, position) { + return Some(res); + } + let (opt_name, search_kind) = if let Some(name) = get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) { @@ -198,6 +202,103 @@ fn get_struct_def_name_for_struct_literal_search( None } +fn try_find_lifetime_references( + syntax: &SyntaxNode, + position: FilePosition, +) -> Option> { + let lifetime_token = + syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::LIFETIME)?; + let parent = lifetime_token.parent(); + + match_ast! { + match parent { + ast::LifetimeArg(it) => find_all_lifetime_references(), + // &'lt self + ast::SelfParam(it) => find_all_lifetime_references(), + ast::RefType(it) => find_all_lifetime_references(), + ast::LifetimeParam(it) => find_all_lifetime_references(),// decl + // HRTB + ast::WherePred(it) => find_all_lifetime_references(), + ast::TypeBound(it) => find_all_lifetime_references(), + ast::Label(_it) => find_all_label_references(position, lifetime_token, &parent), // decl + ast::BreakExpr(_it) => find_all_label_references(position, lifetime_token, &parent), + ast::ContinueExpr(_it) => find_all_label_references(position, lifetime_token, &parent), + _ => None, + } + } +} + +fn find_all_label_references( + position: FilePosition, + lifetime_token: SyntaxToken, + syntax: &SyntaxNode, +) -> Option> { + let label_text = lifetime_token.text(); + let label = syntax.ancestors().find_map(|syn| { + (match_ast! { + match syn { + ast::EffectExpr(it) => it.label(), + ast::LoopExpr(it) => it.label(), + ast::WhileExpr(it) => it.label(), + ast::ForExpr(it) => it.label(), + _ => None, + } + }) + .filter(|label| label.lifetime_token().as_ref().map(|lt| lt.text()) == Some(label_text)) + })?; + let lt = label.lifetime_token()?; + let declaration = Declaration { + nav: NavigationTarget { + file_id: position.file_id, + full_range: lt.text_range(), + focus_range: Some(lt.text_range()), + name: label_text.clone(), + kind: lt.kind(), + container_name: None, + description: None, + docs: None, + }, + kind: ReferenceKind::Label, + access: None, + }; + let label_parent = label.syntax().parent()?; + let expr = match_ast! { + match label_parent { + ast::EffectExpr(it) => it.block_expr()?.syntax().clone(), + ast::LoopExpr(it) => it.loop_body()?.syntax().clone(), + ast::WhileExpr(it) => it.loop_body()?.syntax().clone(), + ast::ForExpr(it) => it.loop_body()?.syntax().clone(), + _ => return None, + } + }; + let references = expr + .descendants() + .filter_map(|syn| { + match_ast! { + match syn { + ast::BreakExpr(it) => it.lifetime_token().filter(|lt| lt.text() == label_text), + ast::ContinueExpr(it) => it.lifetime_token().filter(|lt| lt.text() == label_text), + _ => None, + } + } + }) + .map(|token| Reference { + file_range: FileRange { file_id: position.file_id, range: token.text_range() }, + kind: ReferenceKind::Label, + access: None, + }) + .collect(); + + Some(RangeInfo::new( + lifetime_token.text_range(), + ReferenceSearchResult { declaration, references }, + )) +} + +fn find_all_lifetime_references() -> Option> { + None +} + fn try_find_self_references( syntax: &SyntaxNode, position: FilePosition, @@ -863,6 +964,50 @@ impl Foo { ); } + #[test] + fn test_find_labels() { + check( + r#" +fn main() { + 'foo: loop { + 'bar: while true { + continue 'foo; + } + break 'foo<|>; + } +} +"#, + expect![[r#" + 'foo LIFETIME FileId(0) 16..20 16..20 Label + + FileId(0) 77..81 Label + FileId(0) 107..111 Label + "#]], + ); + } + + #[test] + fn test_find_labels_decl() { + check( + r#" +fn main() { + 'foo<|>: loop { + 'bar: while true { + continue 'foo; + } + break 'foo; + } +} +"#, + expect![[r#" + 'foo LIFETIME FileId(0) 16..20 16..20 Label + + FileId(0) 77..81 Label + FileId(0) 107..111 Label + "#]], + ); + } + fn check(ra_fixture: &str, expect: Expect) { check_with_scope(ra_fixture, None, expect) } diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 607185ca97f4..17873f99a5ba 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -32,6 +32,7 @@ pub enum ReferenceKind { StructLiteral, RecordFieldExprOrPat, SelfKw, + Label, Other, } From 283305fa6bc338f174301080de6a9a688c3db383 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 17:50:13 +0100 Subject: [PATCH 2/6] Support lifetimes in reference search --- crates/ide/src/references.rs | 249 +++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 12 deletions(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 4890185e21e3..bfde3fe6376b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -13,6 +13,7 @@ pub(crate) mod rename; use hir::Semantics; use ide_db::{ + base_db::FileId, defs::{Definition, NameClass, NameRefClass}, search::Reference, search::{ReferenceAccess, ReferenceKind, SearchScope}, @@ -20,8 +21,9 @@ use ide_db::{ }; use syntax::{ algo::find_node_at_offset, - ast::{self, LoopBodyOwner, NameOwner}, - match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TokenAtOffset, + ast::{self, GenericParamsOwner, LoopBodyOwner, NameOwner, TypeBoundsOwner}, + match_ast, AstNode, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, + TokenAtOffset, T, }; use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; @@ -212,14 +214,12 @@ fn try_find_lifetime_references( match_ast! { match parent { - ast::LifetimeArg(it) => find_all_lifetime_references(), - // &'lt self - ast::SelfParam(it) => find_all_lifetime_references(), - ast::RefType(it) => find_all_lifetime_references(), - ast::LifetimeParam(it) => find_all_lifetime_references(),// decl - // HRTB - ast::WherePred(it) => find_all_lifetime_references(), - ast::TypeBound(it) => find_all_lifetime_references(), + ast::LifetimeArg(_it) => find_all_lifetime_references(position, lifetime_token, &parent), + ast::SelfParam(_it) => find_all_lifetime_references(position, lifetime_token, &parent), + ast::RefType(_it) => find_all_lifetime_references(position, lifetime_token, &parent), + ast::LifetimeParam(_it) => find_all_lifetime_references(position, lifetime_token, &parent),// decl + ast::WherePred(_it) => find_all_lifetime_references(position, lifetime_token, &parent), + ast::TypeBound(_it) => find_all_lifetime_references(position, lifetime_token, &parent), ast::Label(_it) => find_all_label_references(position, lifetime_token, &parent), // decl ast::BreakExpr(_it) => find_all_label_references(position, lifetime_token, &parent), ast::ContinueExpr(_it) => find_all_label_references(position, lifetime_token, &parent), @@ -295,8 +295,209 @@ fn find_all_label_references( )) } -fn find_all_lifetime_references() -> Option> { - None +fn find_all_lifetime_references( + position: FilePosition, + lifetime_token: SyntaxToken, + syntax: &SyntaxNode, +) -> Option> { + let lifetime_text = lifetime_token.text(); + // we need to look for something that holds a GenericParamList as this is a definition site for lifetimes + let (lifetime_param, gpl, where_clause) = syntax.ancestors().find_map(|syn| { + let (gpl, where_clause) = match_ast! { + match syn { + ast::Fn(it) => (it.generic_param_list()?, it.where_clause()), + ast::TypeAlias(it) => (it.generic_param_list()?, it.where_clause()), + ast::Struct(it) => (it.generic_param_list()?, it.where_clause()), + ast::Enum(it) => (it.generic_param_list()?, it.where_clause()), + ast::Union(it) => (it.generic_param_list()?, it.where_clause()), + ast::Trait(it) => (it.generic_param_list()?, it.where_clause()), + ast::Impl(it) => (it.generic_param_list()?, it.where_clause()), + ast::WherePred(it) => (it.generic_param_list()?, None), + ast::ForType(it) => (it.generic_param_list()?, None), + _ => return None, + } + }; + Some(( + gpl.lifetime_params().find(|tp| { + tp.lifetime_token().as_ref().map(|lt| lt.text()) == Some(lifetime_text) + })?, + gpl, + where_clause, + )) + })?; + let lt = lifetime_param.lifetime_token()?; + let declaration = Declaration { + nav: NavigationTarget { + file_id: position.file_id, + full_range: lt.text_range(), + focus_range: Some(lt.text_range()), + name: lifetime_text.clone(), + kind: lt.kind(), + container_name: None, + description: None, + docs: None, + }, + kind: ReferenceKind::Label, + access: None, + }; + let gpl_parent = gpl.syntax().parent()?; + let file_id = position.file_id; + let mut references = Vec::new(); + match_ast! { + match gpl_parent { + ast::Fn(it) => { + if let Some(param_list) = it.param_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, param_list.syntax()); + } + if let Some(ret_type) = it.ret_type() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, ret_type.syntax()); + } + if let Some(body) = it.body() { + find_lifetime_references_in_fn(&mut references, lifetime_text, file_id, &body); + } + }, + ast::TypeAlias(it) => { + if let Some(type_bound_list) = it.type_bound_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, type_bound_list.syntax()); + } + if let Some(ty) = it.ty() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + } + }, + ast::Struct(it) => if let Some(field_list) = it.field_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, field_list.syntax()); + }, + ast::Enum(it) => if let Some(variant_list) = it.variant_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, variant_list.syntax()); + }, + ast::Union(it) => if let Some(record_field_list) = it.record_field_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, record_field_list.syntax()); + }, + ast::Trait(it) => { + if let Some(type_bound_list) = it.type_bound_list() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, type_bound_list.syntax()); + } + if let Some(assoc_item_list) = it.assoc_item_list() { + find_lifetime_references_assoc_list(&mut references, lifetime_text, file_id, &assoc_item_list); + } + }, + ast::Impl(it) => if let Some(assoc_item_list) = it.assoc_item_list() { + find_lifetime_references_assoc_list(&mut references, lifetime_text, file_id, &assoc_item_list); + }, + ast::WherePred(it) => if let Some(ty) = it.ty() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + }, + ast::ForType(it) => if let Some(ty) = it.ty() { + find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + }, + _ => return None, + } + }; + + for param in gpl.generic_params().filter(|gp| !matches!(gp, ast::GenericParam::LifetimeParam(lp) if lp.lifetime_token().as_ref() == Some(<))) { + find_lifetime_references_in( + &mut references, + lifetime_text, + position.file_id, + param.syntax(), + ); + } + if let Some(where_clause) = where_clause { + for predicate in where_clause.predicates() { + find_lifetime_references_in( + &mut references, + lifetime_text, + position.file_id, + predicate.syntax(), + ); + } + } + + Some(RangeInfo::new( + lifetime_token.text_range(), + ReferenceSearchResult { declaration, references }, + )) +} + +fn find_lifetime_references_in( + references: &mut Vec, + lifetime_text: &SmolStr, + file_id: FileId, + syntax: &SyntaxNode, +) { + references.extend(syntax.descendants_with_tokens().filter_map(|ele| match ele { + NodeOrToken::Token(token) if token.text() == lifetime_text => Some(Reference { + file_range: FileRange { file_id, range: token.text_range() }, + kind: ReferenceKind::Label, + access: None, + }), + _ => None, + })); +} + +fn find_lifetime_references_assoc_list( + references: &mut Vec, + lifetime_text: &SmolStr, + file_id: FileId, + assoc_items: &ast::AssocItemList, +) { + for assoc_item in assoc_items.assoc_items() { + match assoc_item { + ast::AssocItem::Fn(fn_) => { + if let Some(fn_) = fn_.body() { + find_lifetime_references_in_fn(references, lifetime_text, file_id, &fn_); + } + } + _ => { + find_lifetime_references_in(references, lifetime_text, file_id, assoc_item.syntax()) + } + } + } +} + +fn find_lifetime_references_in_fn( + references: &mut Vec, + lifetime_text: &SmolStr, + file_id: FileId, + body: &ast::BlockExpr, +) { + let items = [ + SyntaxKind::STRUCT, + SyntaxKind::ENUM, + SyntaxKind::FN, + SyntaxKind::UNION, + SyntaxKind::TYPE_ALIAS, + SyntaxKind::IMPL, + SyntaxKind::TRAIT, + ]; + let mut skip_until = None; + for event in body.syntax().preorder_with_tokens() { + if let Some(kind) = skip_until { + if matches!(event, + syntax::WalkEvent::Leave(NodeOrToken::Node(node)) if node.kind() == kind) + { + skip_until = None + } + continue; + } + match event { + syntax::WalkEvent::Enter(NodeOrToken::Node(node)) => { + if items.contains(&node.kind()) { + skip_until = Some(node.kind()); + } + } + syntax::WalkEvent::Enter(NodeOrToken::Token(token)) + if token.kind() == T![lifetime] && token.text() == lifetime_text => + { + references.push(Reference { + file_range: FileRange { file_id, range: token.text_range() }, + kind: ReferenceKind::Label, + access: None, + }); + } + _ => (), + } + } } fn try_find_self_references( @@ -1008,6 +1209,30 @@ fn main() { ); } + #[test] + fn test_find_lifetimes() { + check( + r#" +trait Foo<'a> {} +impl<'a> Foo<'a> for &'a () {} + +fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { + fn bar<'a>(_: &'a ()) {} + x +} +"#, + expect![[r#" + 'a LIFETIME FileId(0) 56..58 56..58 Label + + FileId(0) 72..74 Label + FileId(0) 83..85 Label + FileId(0) 64..66 Label + FileId(0) 96..98 Label + FileId(0) 107..109 Label + "#]], + ); + } + fn check(ra_fixture: &str, expect: Expect) { check_with_scope(ra_fixture, None, expect) } From f229eb18a56d3857047cad51b114448f7873e25a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 18:02:39 +0100 Subject: [PATCH 3/6] Cleanup lifetime reference search --- crates/ide/src/references.rs | 196 +++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 88 deletions(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index bfde3fe6376b..df8b9e285232 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -23,7 +23,7 @@ use syntax::{ algo::find_node_at_offset, ast::{self, GenericParamsOwner, LoopBodyOwner, NameOwner, TypeBoundsOwner}, match_ast, AstNode, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, - TokenAtOffset, T, + TokenAtOffset, WalkEvent, T, }; use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; @@ -302,29 +302,30 @@ fn find_all_lifetime_references( ) -> Option> { let lifetime_text = lifetime_token.text(); // we need to look for something that holds a GenericParamList as this is a definition site for lifetimes - let (lifetime_param, gpl, where_clause) = syntax.ancestors().find_map(|syn| { - let (gpl, where_clause) = match_ast! { - match syn { - ast::Fn(it) => (it.generic_param_list()?, it.where_clause()), - ast::TypeAlias(it) => (it.generic_param_list()?, it.where_clause()), - ast::Struct(it) => (it.generic_param_list()?, it.where_clause()), - ast::Enum(it) => (it.generic_param_list()?, it.where_clause()), - ast::Union(it) => (it.generic_param_list()?, it.where_clause()), - ast::Trait(it) => (it.generic_param_list()?, it.where_clause()), - ast::Impl(it) => (it.generic_param_list()?, it.where_clause()), - ast::WherePred(it) => (it.generic_param_list()?, None), - ast::ForType(it) => (it.generic_param_list()?, None), - _ => return None, - } - }; - Some(( - gpl.lifetime_params().find(|tp| { - tp.lifetime_token().as_ref().map(|lt| lt.text()) == Some(lifetime_text) - })?, - gpl, - where_clause, - )) - })?; + let (lifetime_param, generic_param_list, where_clause) = + syntax.ancestors().find_map(|syn| { + let (gpl, where_clause) = match_ast! { + match syn { + ast::Fn(it) => (it.generic_param_list()?, it.where_clause()), + ast::TypeAlias(it) => (it.generic_param_list()?, it.where_clause()), + ast::Struct(it) => (it.generic_param_list()?, it.where_clause()), + ast::Enum(it) => (it.generic_param_list()?, it.where_clause()), + ast::Union(it) => (it.generic_param_list()?, it.where_clause()), + ast::Trait(it) => (it.generic_param_list()?, it.where_clause()), + ast::Impl(it) => (it.generic_param_list()?, it.where_clause()), + ast::WherePred(it) => (it.generic_param_list()?, None), + ast::ForType(it) => (it.generic_param_list()?, None), + _ => return None, + } + }; + Some(( + gpl.lifetime_params().find(|tp| { + tp.lifetime_token().as_ref().map(|lt| lt.text()) == Some(lifetime_text) + })?, + gpl, + where_clause, + )) + })?; let lt = lifetime_param.lifetime_token()?; let declaration = Declaration { nav: NavigationTarget { @@ -340,83 +341,99 @@ fn find_all_lifetime_references( kind: ReferenceKind::Label, access: None, }; - let gpl_parent = gpl.syntax().parent()?; - let file_id = position.file_id; + let gpl_parent = generic_param_list.syntax().parent()?; let mut references = Vec::new(); + + // find references in the GenericParamList itself + for param in generic_param_list.generic_params().filter(|gp| { + !matches!( + gp, + ast::GenericParam::LifetimeParam(lp) if lp.lifetime_token().as_ref() == Some(<) + ) + }) { + find_lifetime_references_in( + &mut references, + lifetime_text, + position.file_id, + param.syntax(), + ); + } + // find references in the WhereClause if it exists + if let Some(where_clause) = where_clause { + for predicate in where_clause.predicates() { + find_lifetime_references_in( + &mut references, + lifetime_text, + position.file_id, + predicate.syntax(), + ); + } + } + // find references in the other inner nodes of whatever we are in + find_lifetime_references(&mut references, lifetime_text, position.file_id, gpl_parent); + + Some(RangeInfo::new( + lifetime_token.text_range(), + ReferenceSearchResult { declaration, references }, + )) +} + +fn find_lifetime_references( + references: &mut Vec, + lifetime_text: &SmolStr, + file_id: FileId, + gpl_parent: SyntaxNode, +) { match_ast! { match gpl_parent { ast::Fn(it) => { if let Some(param_list) = it.param_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, param_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, param_list.syntax()); } if let Some(ret_type) = it.ret_type() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, ret_type.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, ret_type.syntax()); } if let Some(body) = it.body() { - find_lifetime_references_in_fn(&mut references, lifetime_text, file_id, &body); + find_lifetime_references_in_fn(references, lifetime_text, file_id, &body); } }, ast::TypeAlias(it) => { if let Some(type_bound_list) = it.type_bound_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, type_bound_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, type_bound_list.syntax()); } if let Some(ty) = it.ty() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, ty.syntax()); } }, ast::Struct(it) => if let Some(field_list) = it.field_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, field_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, field_list.syntax()); }, ast::Enum(it) => if let Some(variant_list) = it.variant_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, variant_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, variant_list.syntax()); }, ast::Union(it) => if let Some(record_field_list) = it.record_field_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, record_field_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, record_field_list.syntax()); }, ast::Trait(it) => { if let Some(type_bound_list) = it.type_bound_list() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, type_bound_list.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, type_bound_list.syntax()); } if let Some(assoc_item_list) = it.assoc_item_list() { - find_lifetime_references_assoc_list(&mut references, lifetime_text, file_id, &assoc_item_list); + find_lifetime_references_in_assoc_list(references, lifetime_text, file_id, &assoc_item_list); } }, ast::Impl(it) => if let Some(assoc_item_list) = it.assoc_item_list() { - find_lifetime_references_assoc_list(&mut references, lifetime_text, file_id, &assoc_item_list); + find_lifetime_references_in_assoc_list(references, lifetime_text, file_id, &assoc_item_list); }, ast::WherePred(it) => if let Some(ty) = it.ty() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, ty.syntax()); }, ast::ForType(it) => if let Some(ty) = it.ty() { - find_lifetime_references_in(&mut references, lifetime_text, file_id, ty.syntax()); + find_lifetime_references_in(references, lifetime_text, file_id, ty.syntax()); }, - _ => return None, + _ => (), } }; - - for param in gpl.generic_params().filter(|gp| !matches!(gp, ast::GenericParam::LifetimeParam(lp) if lp.lifetime_token().as_ref() == Some(<))) { - find_lifetime_references_in( - &mut references, - lifetime_text, - position.file_id, - param.syntax(), - ); - } - if let Some(where_clause) = where_clause { - for predicate in where_clause.predicates() { - find_lifetime_references_in( - &mut references, - lifetime_text, - position.file_id, - predicate.syntax(), - ); - } - } - - Some(RangeInfo::new( - lifetime_token.text_range(), - ReferenceSearchResult { declaration, references }, - )) } fn find_lifetime_references_in( @@ -435,7 +452,7 @@ fn find_lifetime_references_in( })); } -fn find_lifetime_references_assoc_list( +fn find_lifetime_references_in_assoc_list( references: &mut Vec, lifetime_text: &SmolStr, file_id: FileId, @@ -444,8 +461,8 @@ fn find_lifetime_references_assoc_list( for assoc_item in assoc_items.assoc_items() { match assoc_item { ast::AssocItem::Fn(fn_) => { - if let Some(fn_) = fn_.body() { - find_lifetime_references_in_fn(references, lifetime_text, file_id, &fn_); + if let Some(body) = fn_.body() { + find_lifetime_references_in_fn(references, lifetime_text, file_id, &body); } } _ => { @@ -461,6 +478,8 @@ fn find_lifetime_references_in_fn( file_id: FileId, body: &ast::BlockExpr, ) { + // skip inner items inside this function as they may redeclare a lifetime with the same name + // as the one we are looking for let items = [ SyntaxKind::STRUCT, SyntaxKind::ENUM, @@ -473,29 +492,30 @@ fn find_lifetime_references_in_fn( let mut skip_until = None; for event in body.syntax().preorder_with_tokens() { if let Some(kind) = skip_until { - if matches!(event, - syntax::WalkEvent::Leave(NodeOrToken::Node(node)) if node.kind() == kind) - { + if matches!( + event, + WalkEvent::Leave(NodeOrToken::Node(node)) if node.kind() == kind + ) { skip_until = None } - continue; - } - match event { - syntax::WalkEvent::Enter(NodeOrToken::Node(node)) => { - if items.contains(&node.kind()) { - skip_until = Some(node.kind()); + } else { + match event { + WalkEvent::Enter(NodeOrToken::Node(node)) => { + if items.contains(&node.kind()) { + skip_until = Some(node.kind()); + } } + WalkEvent::Enter(NodeOrToken::Token(token)) + if token.kind() == T![lifetime] && token.text() == lifetime_text => + { + references.push(Reference { + file_range: FileRange { file_id, range: token.text_range() }, + kind: ReferenceKind::Label, + access: None, + }); + } + _ => (), } - syntax::WalkEvent::Enter(NodeOrToken::Token(token)) - if token.kind() == T![lifetime] && token.text() == lifetime_text => - { - references.push(Reference { - file_range: FileRange { file_id, range: token.text_range() }, - kind: ReferenceKind::Label, - access: None, - }); - } - _ => (), } } } @@ -1224,11 +1244,11 @@ fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { expect![[r#" 'a LIFETIME FileId(0) 56..58 56..58 Label - FileId(0) 72..74 Label - FileId(0) 83..85 Label FileId(0) 64..66 Label FileId(0) 96..98 Label FileId(0) 107..109 Label + FileId(0) 72..74 Label + FileId(0) 83..85 Label "#]], ); } From 6b80c66a809614c5a93ab5d0a8bafecc047b7667 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 18:23:52 +0100 Subject: [PATCH 4/6] Add some lifetime reference serach tests --- crates/ide/src/references.rs | 70 +++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index df8b9e285232..1b4a06dfc64b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -422,8 +422,16 @@ fn find_lifetime_references( find_lifetime_references_in_assoc_list(references, lifetime_text, file_id, &assoc_item_list); } }, - ast::Impl(it) => if let Some(assoc_item_list) = it.assoc_item_list() { - find_lifetime_references_in_assoc_list(references, lifetime_text, file_id, &assoc_item_list); + ast::Impl(it) => { + if let Some(trait_) = it.trait_() { + find_lifetime_references_in(references, lifetime_text, file_id, trait_.syntax()); + } + if let Some(self_ty) = it.self_ty() { + find_lifetime_references_in(references, lifetime_text, file_id, self_ty.syntax()); + } + if let Some(assoc_item_list) = it.assoc_item_list() { + find_lifetime_references_in_assoc_list(references, lifetime_text, file_id, &assoc_item_list); + } }, ast::WherePred(it) => if let Some(ty) = it.ty() { find_lifetime_references_in(references, lifetime_text, file_id, ty.syntax()); @@ -459,9 +467,22 @@ fn find_lifetime_references_in_assoc_list( assoc_items: &ast::AssocItemList, ) { for assoc_item in assoc_items.assoc_items() { + // assoc items can't shadow lifetime variables match assoc_item { - ast::AssocItem::Fn(fn_) => { - if let Some(body) = fn_.body() { + ast::AssocItem::Fn(it) => { + if let Some(it) = it.generic_param_list() { + find_lifetime_references_in(references, lifetime_text, file_id, it.syntax()); + } + if let Some(it) = it.param_list() { + find_lifetime_references_in(references, lifetime_text, file_id, it.syntax()); + } + if let Some(it) = it.ret_type() { + find_lifetime_references_in(references, lifetime_text, file_id, it.syntax()); + } + if let Some(it) = it.where_clause() { + find_lifetime_references_in(references, lifetime_text, file_id, it.syntax()); + } + if let Some(body) = it.body() { find_lifetime_references_in_fn(references, lifetime_text, file_id, &body); } } @@ -1230,7 +1251,7 @@ fn main() { } #[test] - fn test_find_lifetimes() { + fn test_find_lifetimes_function() { check( r#" trait Foo<'a> {} @@ -1253,6 +1274,45 @@ fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { ); } + #[test] + fn test_find_type_alias() { + check( + r#" +type Foo<'a, T> where T: 'a<|> = &'a T; +"#, + expect![[r#" + 'a LIFETIME FileId(0) 9..11 9..11 Label + + FileId(0) 25..27 Label + FileId(0) 31..33 Label + "#]], + ); + } + + #[test] + fn test_find_trait_impl() { + check( + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} + +impl<'a> Foo<'a> for &'a () { + fn foo() -> &'a<|> () { + unimplemented!() + } +} +"#, + expect![[r#" + 'a LIFETIME FileId(0) 48..50 48..50 Label + + FileId(0) 56..58 Label + FileId(0) 65..67 Label + FileId(0) 90..92 Label + "#]], + ); + } + fn check(ra_fixture: &str, expect: Expect) { check_with_scope(ra_fixture, None, expect) } From e52c182df4b1aa9204b5791602cce1d7b2ea864d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 18:41:31 +0100 Subject: [PATCH 5/6] Add a few comments to lifetime reference search --- crates/ide/src/references.rs | 130 ++++++++++++++++++++--------------- crates/ide_db/src/search.rs | 2 +- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 1b4a06dfc64b..39beed3fdb03 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -236,6 +236,7 @@ fn find_all_label_references( let label_text = lifetime_token.text(); let label = syntax.ancestors().find_map(|syn| { (match_ast! { + // all of these expressions declare loop labels match syn { ast::EffectExpr(it) => it.label(), ast::LoopExpr(it) => it.label(), @@ -246,21 +247,6 @@ fn find_all_label_references( }) .filter(|label| label.lifetime_token().as_ref().map(|lt| lt.text()) == Some(label_text)) })?; - let lt = label.lifetime_token()?; - let declaration = Declaration { - nav: NavigationTarget { - file_id: position.file_id, - full_range: lt.text_range(), - focus_range: Some(lt.text_range()), - name: label_text.clone(), - kind: lt.kind(), - container_name: None, - description: None, - docs: None, - }, - kind: ReferenceKind::Label, - access: None, - }; let label_parent = label.syntax().parent()?; let expr = match_ast! { match label_parent { @@ -275,6 +261,7 @@ fn find_all_label_references( .descendants() .filter_map(|syn| { match_ast! { + // only these expressions may refer to labels match syn { ast::BreakExpr(it) => it.lifetime_token().filter(|lt| lt.text() == label_text), ast::ContinueExpr(it) => it.lifetime_token().filter(|lt| lt.text() == label_text), @@ -284,11 +271,26 @@ fn find_all_label_references( }) .map(|token| Reference { file_range: FileRange { file_id: position.file_id, range: token.text_range() }, - kind: ReferenceKind::Label, + kind: ReferenceKind::Lifetime, access: None, }) .collect(); + let lt = label.lifetime_token()?; + let declaration = Declaration { + nav: NavigationTarget { + file_id: position.file_id, + full_range: lt.text_range(), + focus_range: Some(lt.text_range()), + name: label_text.clone(), + kind: lt.kind(), + container_name: None, + description: None, + docs: None, + }, + kind: ReferenceKind::Lifetime, + access: None, + }; Some(RangeInfo::new( lifetime_token.text_range(), ReferenceSearchResult { declaration, references }, @@ -301,7 +303,7 @@ fn find_all_lifetime_references( syntax: &SyntaxNode, ) -> Option> { let lifetime_text = lifetime_token.text(); - // we need to look for something that holds a GenericParamList as this is a definition site for lifetimes + // we need to look for something that holds a GenericParamList as this is the declaration site for lifetimes let (lifetime_param, generic_param_list, where_clause) = syntax.ancestors().find_map(|syn| { let (gpl, where_clause) = match_ast! { @@ -327,21 +329,6 @@ fn find_all_lifetime_references( )) })?; let lt = lifetime_param.lifetime_token()?; - let declaration = Declaration { - nav: NavigationTarget { - file_id: position.file_id, - full_range: lt.text_range(), - focus_range: Some(lt.text_range()), - name: lifetime_text.clone(), - kind: lt.kind(), - container_name: None, - description: None, - docs: None, - }, - kind: ReferenceKind::Label, - access: None, - }; - let gpl_parent = generic_param_list.syntax().parent()?; let mut references = Vec::new(); // find references in the GenericParamList itself @@ -369,16 +356,38 @@ fn find_all_lifetime_references( ); } } + let gpl_parent = generic_param_list.syntax().parent()?; // find references in the other inner nodes of whatever we are in - find_lifetime_references(&mut references, lifetime_text, position.file_id, gpl_parent); + find_lifetime_references_in_declaring_node( + &mut references, + lifetime_text, + position.file_id, + gpl_parent, + ); + let declaration = Declaration { + nav: NavigationTarget { + file_id: position.file_id, + full_range: lt.text_range(), + focus_range: Some(lt.text_range()), + name: lifetime_text.clone(), + kind: lt.kind(), + container_name: None, + description: None, + docs: None, + }, + kind: ReferenceKind::Lifetime, + access: None, + }; Some(RangeInfo::new( lifetime_token.text_range(), ReferenceSearchResult { declaration, references }, )) } -fn find_lifetime_references( +/// Searches for lifetime references in the node that declared the lifetime ignoring its WhereClause +/// and GenericParamlist. +fn find_lifetime_references_in_declaring_node( references: &mut Vec, lifetime_text: &SmolStr, file_id: FileId, @@ -444,6 +453,7 @@ fn find_lifetime_references( }; } +/// Searches for lifetime references in the given syntax node by doing token text matching. fn find_lifetime_references_in( references: &mut Vec, lifetime_text: &SmolStr, @@ -453,13 +463,15 @@ fn find_lifetime_references_in( references.extend(syntax.descendants_with_tokens().filter_map(|ele| match ele { NodeOrToken::Token(token) if token.text() == lifetime_text => Some(Reference { file_range: FileRange { file_id, range: token.text_range() }, - kind: ReferenceKind::Label, + kind: ReferenceKind::Lifetime, access: None, }), _ => None, })); } +/// Searches for lifetimes in an AssocItemList from an Impl or Trait, this is required to delegate +/// the special searching for associated functions via [`find_lifetime_references_in_fn`]. fn find_lifetime_references_in_assoc_list( references: &mut Vec, lifetime_text: &SmolStr, @@ -486,6 +498,7 @@ fn find_lifetime_references_in_assoc_list( find_lifetime_references_in_fn(references, lifetime_text, file_id, &body); } } + ast::AssocItem::MacroCall(_) => (/* FIXME */), _ => { find_lifetime_references_in(references, lifetime_text, file_id, assoc_item.syntax()) } @@ -493,14 +506,17 @@ fn find_lifetime_references_in_assoc_list( } } +/// Searches for the given lifetime text in the functions body. This is special cased to skip inner +/// defined items as these can shadow the lifetime we are looking for. fn find_lifetime_references_in_fn( references: &mut Vec, lifetime_text: &SmolStr, file_id: FileId, body: &ast::BlockExpr, ) { - // skip inner items inside this function as they may redeclare a lifetime with the same name - // as the one we are looking for + // skip inner items inside the function as they may redeclare a lifetime with the same name + // as the one we are looking for, inner items may also not re-use outer generics without redeclaring + // them so they are safe to skip let items = [ SyntaxKind::STRUCT, SyntaxKind::ENUM, @@ -531,7 +547,7 @@ fn find_lifetime_references_in_fn( { references.push(Reference { file_range: FileRange { file_id, range: token.text_range() }, - kind: ReferenceKind::Label, + kind: ReferenceKind::Lifetime, access: None, }); } @@ -1220,10 +1236,10 @@ fn main() { } "#, expect![[r#" - 'foo LIFETIME FileId(0) 16..20 16..20 Label + 'foo LIFETIME FileId(0) 16..20 16..20 Lifetime - FileId(0) 77..81 Label - FileId(0) 107..111 Label + FileId(0) 77..81 Lifetime + FileId(0) 107..111 Lifetime "#]], ); } @@ -1242,10 +1258,10 @@ fn main() { } "#, expect![[r#" - 'foo LIFETIME FileId(0) 16..20 16..20 Label + 'foo LIFETIME FileId(0) 16..20 16..20 Lifetime - FileId(0) 77..81 Label - FileId(0) 107..111 Label + FileId(0) 77..81 Lifetime + FileId(0) 107..111 Lifetime "#]], ); } @@ -1263,13 +1279,13 @@ fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { } "#, expect![[r#" - 'a LIFETIME FileId(0) 56..58 56..58 Label + 'a LIFETIME FileId(0) 56..58 56..58 Lifetime - FileId(0) 64..66 Label - FileId(0) 96..98 Label - FileId(0) 107..109 Label - FileId(0) 72..74 Label - FileId(0) 83..85 Label + FileId(0) 64..66 Lifetime + FileId(0) 96..98 Lifetime + FileId(0) 107..109 Lifetime + FileId(0) 72..74 Lifetime + FileId(0) 83..85 Lifetime "#]], ); } @@ -1281,10 +1297,10 @@ fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { type Foo<'a, T> where T: 'a<|> = &'a T; "#, expect![[r#" - 'a LIFETIME FileId(0) 9..11 9..11 Label + 'a LIFETIME FileId(0) 9..11 9..11 Lifetime - FileId(0) 25..27 Label - FileId(0) 31..33 Label + FileId(0) 25..27 Lifetime + FileId(0) 31..33 Lifetime "#]], ); } @@ -1304,11 +1320,11 @@ impl<'a> Foo<'a> for &'a () { } "#, expect![[r#" - 'a LIFETIME FileId(0) 48..50 48..50 Label + 'a LIFETIME FileId(0) 48..50 48..50 Lifetime - FileId(0) 56..58 Label - FileId(0) 65..67 Label - FileId(0) 90..92 Label + FileId(0) 56..58 Lifetime + FileId(0) 65..67 Lifetime + FileId(0) 90..92 Lifetime "#]], ); } diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 17873f99a5ba..6a2f97346405 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -32,7 +32,7 @@ pub enum ReferenceKind { StructLiteral, RecordFieldExprOrPat, SelfKw, - Label, + Lifetime, Other, } From fe8ab123837c77291e49d5712481abf1663816f5 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 9 Dec 2020 22:26:38 +0100 Subject: [PATCH 6/6] Prevent lifetime renaming for now --- crates/ide/src/references/rename.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 64fe8bd654ca..8d3e470bdff7 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs @@ -360,6 +360,10 @@ fn rename_reference( None => return Err(RenameError("No references found at position".to_string())), }; + if refs.declaration().kind == ReferenceKind::Lifetime { + return Err(RenameError("Renaming references is not yet suported".to_string())); + } + let edit = refs .into_iter() .map(|reference| source_edit_from_reference(sema, reference, new_name))