mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
Merge #6907
6907: Lifetime reference search r=matklad a=Veykril PR #6787 but rewritten to make use of the HIR now. This only applies to Lifetimes, not labels. Also Higher-Ranked Trait Bounds aren't supported yet, but I feel like this PR is big enough as is which is why I left them out after noticing I forgot about them. Supporting renaming required slight changes in the renaming module as lifetime names aren't allowed for anything but lifetimes(and labels) and vice versa for normal names. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
d641bccb0f
12 changed files with 373 additions and 41 deletions
|
@ -9,7 +9,7 @@ use ide_db::{defs::Definition, RootDatabase};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, NameOwner},
|
ast::{self, NameOwner},
|
||||||
match_ast, AstNode, SmolStr,
|
match_ast, AstNode, SmolStr,
|
||||||
SyntaxKind::{self, IDENT_PAT, TYPE_PARAM},
|
SyntaxKind::{self, IDENT_PAT, LIFETIME_PARAM, TYPE_PARAM},
|
||||||
TextRange,
|
TextRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ impl TryToNav for Definition {
|
||||||
Definition::SelfType(it) => Some(it.to_nav(db)),
|
Definition::SelfType(it) => Some(it.to_nav(db)),
|
||||||
Definition::Local(it) => Some(it.to_nav(db)),
|
Definition::Local(it) => Some(it.to_nav(db)),
|
||||||
Definition::TypeParam(it) => Some(it.to_nav(db)),
|
Definition::TypeParam(it) => Some(it.to_nav(db)),
|
||||||
|
Definition::LifetimeParam(it) => Some(it.to_nav(db)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,6 +377,23 @@ impl ToNav for hir::TypeParam {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToNav for hir::LifetimeParam {
|
||||||
|
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||||
|
let src = self.source(db);
|
||||||
|
let full_range = src.value.syntax().text_range();
|
||||||
|
NavigationTarget {
|
||||||
|
file_id: src.file_id.original_file(db),
|
||||||
|
name: self.name(db).to_string().into(),
|
||||||
|
kind: LIFETIME_PARAM,
|
||||||
|
full_range,
|
||||||
|
focus_range: Some(full_range),
|
||||||
|
container_name: None,
|
||||||
|
description: None,
|
||||||
|
docs: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> {
|
pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> {
|
||||||
let parse = db.parse(symbol.file_id);
|
let parse = db.parse(symbol.file_id);
|
||||||
let node = symbol.ptr.to_node(parse.tree().syntax());
|
let node = symbol.ptr.to_node(parse.tree().syntax());
|
||||||
|
|
|
@ -190,7 +190,10 @@ fn rewrite_intra_doc_link(
|
||||||
},
|
},
|
||||||
Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
|
Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
|
||||||
Definition::Field(it) => it.resolve_doc_path(db, link, ns),
|
Definition::Field(it) => it.resolve_doc_path(db, link, ns),
|
||||||
Definition::SelfType(_) | Definition::Local(_) | Definition::TypeParam(_) => return None,
|
Definition::SelfType(_)
|
||||||
|
| Definition::Local(_)
|
||||||
|
| Definition::TypeParam(_)
|
||||||
|
| Definition::LifetimeParam(_) => return None,
|
||||||
}?;
|
}?;
|
||||||
let krate = resolved.module(db)?.krate();
|
let krate = resolved.module(db)?.krate();
|
||||||
let canonical_path = resolved.canonical_path(db)?;
|
let canonical_path = resolved.canonical_path(db)?;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use either::Either;
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::FileId,
|
base_db::FileId,
|
||||||
|
@ -33,7 +34,7 @@ pub(crate) fn goto_definition(
|
||||||
let nav_targets = match_ast! {
|
let nav_targets = match_ast! {
|
||||||
match parent {
|
match parent {
|
||||||
ast::NameRef(name_ref) => {
|
ast::NameRef(name_ref) => {
|
||||||
reference_definition(&sema, &name_ref).to_vec()
|
reference_definition(&sema, Either::Right(&name_ref)).to_vec()
|
||||||
},
|
},
|
||||||
ast::Name(name) => {
|
ast::Name(name) => {
|
||||||
let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
|
let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
|
||||||
|
@ -53,6 +54,13 @@ pub(crate) fn goto_definition(
|
||||||
let self_param = func.param_list()?.self_param()?;
|
let self_param = func.param_list()?.self_param()?;
|
||||||
vec![self_to_nav_target(self_param, position.file_id)?]
|
vec![self_to_nav_target(self_param, position.file_id)?]
|
||||||
},
|
},
|
||||||
|
ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) {
|
||||||
|
let def = name_class.referenced_or_defined(sema.db);
|
||||||
|
let nav = def.try_to_nav(sema.db)?;
|
||||||
|
vec![nav]
|
||||||
|
} else {
|
||||||
|
reference_definition(&sema, Either::Left(<)).to_vec()
|
||||||
|
},
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -64,7 +72,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||||
return tokens.max_by_key(priority);
|
return tokens.max_by_key(priority);
|
||||||
fn priority(n: &SyntaxToken) -> usize {
|
fn priority(n: &SyntaxToken) -> usize {
|
||||||
match n.kind() {
|
match n.kind() {
|
||||||
IDENT | INT_NUMBER | T![self] => 2,
|
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2,
|
||||||
kind if kind.is_trivia() => 0,
|
kind if kind.is_trivia() => 0,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}
|
||||||
|
@ -102,9 +110,12 @@ impl ReferenceResult {
|
||||||
|
|
||||||
pub(crate) fn reference_definition(
|
pub(crate) fn reference_definition(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
name_ref: &ast::NameRef,
|
name_ref: Either<&ast::Lifetime, &ast::NameRef>,
|
||||||
) -> ReferenceResult {
|
) -> ReferenceResult {
|
||||||
let name_kind = NameRefClass::classify(sema, name_ref);
|
let name_kind = name_ref.either(
|
||||||
|
|lifetime| NameRefClass::classify_lifetime(sema, lifetime),
|
||||||
|
|name_ref| NameRefClass::classify(sema, name_ref),
|
||||||
|
);
|
||||||
if let Some(def) = name_kind {
|
if let Some(def) = name_kind {
|
||||||
let def = def.referenced(sema.db);
|
let def = def.referenced(sema.db);
|
||||||
return match def.try_to_nav(sema.db) {
|
return match def.try_to_nav(sema.db) {
|
||||||
|
@ -114,10 +125,9 @@ pub(crate) fn reference_definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback index based approach:
|
// Fallback index based approach:
|
||||||
let navs = symbol_index::index_resolve(sema.db, name_ref)
|
let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text);
|
||||||
.into_iter()
|
let navs =
|
||||||
.map(|s| s.to_nav(sema.db))
|
symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect();
|
||||||
.collect();
|
|
||||||
ReferenceResult::Approximate(navs)
|
ReferenceResult::Approximate(navs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1033,6 +1043,37 @@ impl Foo {
|
||||||
fn bar(&self<|>) {
|
fn bar(&self<|>) {
|
||||||
//^^^^
|
//^^^^
|
||||||
}
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_lifetime_param_on_decl() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn foo<'foobar<|>>(_: &'foobar ()) {
|
||||||
|
//^^^^^^^
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_lifetime_param_decl() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn foo<'foobar>(_: &'foobar<|> ()) {
|
||||||
|
//^^^^^^^
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_lifetime_param_decl_nested() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn foo<'foobar>(_: &'foobar ()) {
|
||||||
|
fn foo<'foobar>(_: &'foobar<|> ()) {}
|
||||||
|
//^^^^^^^
|
||||||
}"#,
|
}"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,7 +364,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
|
||||||
Adt::Enum(it) => from_def_source(db, it, mod_path),
|
Adt::Enum(it) => from_def_source(db, it, mod_path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Definition::TypeParam(_) => {
|
Definition::TypeParam(_) | Definition::LifetimeParam(_) => {
|
||||||
// FIXME: Hover for generic param
|
// FIXME: Hover for generic param
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,6 +528,13 @@ impl Analysis {
|
||||||
self.with_db(|db| references::rename::rename(db, position, new_name))
|
self.with_db(|db| references::rename::rename(db, position, new_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepare_rename(
|
||||||
|
&self,
|
||||||
|
position: FilePosition,
|
||||||
|
) -> Cancelable<Result<RangeInfo<()>, RenameError>> {
|
||||||
|
self.with_db(|db| references::rename::prepare_rename(db, position))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn structural_search_replace(
|
pub fn structural_search_replace(
|
||||||
&self,
|
&self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
|
|
@ -130,6 +130,8 @@ pub(crate) fn find_all_refs(
|
||||||
kind = ReferenceKind::FieldShorthandForLocal;
|
kind = ReferenceKind::FieldShorthandForLocal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Definition::LifetimeParam(_) = def {
|
||||||
|
kind = ReferenceKind::Lifetime;
|
||||||
};
|
};
|
||||||
|
|
||||||
let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
|
let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
|
||||||
|
@ -148,11 +150,29 @@ fn find_name(
|
||||||
let range = name.syntax().text_range();
|
let range = name.syntax().text_range();
|
||||||
return Some(RangeInfo::new(range, def));
|
return Some(RangeInfo::new(range, def));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (text_range, def) = if let Some(lifetime) =
|
||||||
|
sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset)
|
||||||
|
{
|
||||||
|
if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime)
|
||||||
|
.map(|class| NameRefClass::referenced(class, sema.db))
|
||||||
|
{
|
||||||
|
(lifetime.syntax().text_range(), def)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
lifetime.syntax().text_range(),
|
||||||
|
NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let name_ref =
|
let name_ref =
|
||||||
sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
|
sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
|
||||||
let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db);
|
(
|
||||||
let range = name_ref.syntax().text_range();
|
name_ref.syntax().text_range(),
|
||||||
Some(RangeInfo::new(range, def))
|
NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Some(RangeInfo::new(text_range, def))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
|
fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
|
||||||
|
@ -1005,4 +1025,65 @@ impl Foo {
|
||||||
}
|
}
|
||||||
expect.assert_eq(&actual)
|
expect.assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_lifetimes_function() {
|
||||||
|
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_PARAM FileId(0) 55..57 55..57 Lifetime
|
||||||
|
|
||||||
|
FileId(0) 63..65 Lifetime
|
||||||
|
FileId(0) 71..73 Lifetime
|
||||||
|
FileId(0) 82..84 Lifetime
|
||||||
|
FileId(0) 95..97 Lifetime
|
||||||
|
FileId(0) 106..108 Lifetime
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_lifetimes_type_alias() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
type Foo<'a, T> where T: 'a<|> = &'a T;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
'a LIFETIME_PARAM FileId(0) 9..11 9..11 Lifetime
|
||||||
|
|
||||||
|
FileId(0) 25..27 Lifetime
|
||||||
|
FileId(0) 31..33 Lifetime
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_lifetimes_trait_impl() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
trait Foo<'a> {
|
||||||
|
fn foo() -> &'a ();
|
||||||
|
}
|
||||||
|
impl<'a> Foo<'a> for &'a () {
|
||||||
|
fn foo() -> &'a<|> () {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
'a LIFETIME_PARAM FileId(0) 47..49 47..49 Lifetime
|
||||||
|
|
||||||
|
FileId(0) 55..57 Lifetime
|
||||||
|
FileId(0) 64..66 Lifetime
|
||||||
|
FileId(0) 89..91 Lifetime
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,29 @@ impl fmt::Display for RenameError {
|
||||||
|
|
||||||
impl Error for RenameError {}
|
impl Error for RenameError {}
|
||||||
|
|
||||||
|
pub(crate) fn prepare_rename(
|
||||||
|
db: &RootDatabase,
|
||||||
|
position: FilePosition,
|
||||||
|
) -> Result<RangeInfo<()>, RenameError> {
|
||||||
|
let sema = Semantics::new(db);
|
||||||
|
let source_file = sema.parse(position.file_id);
|
||||||
|
let syntax = source_file.syntax();
|
||||||
|
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
|
||||||
|
rename_mod(&sema, position, module, "dummy")
|
||||||
|
} else if let Some(self_token) =
|
||||||
|
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
|
||||||
|
{
|
||||||
|
rename_self_to_param(&sema, position, self_token, "dummy")
|
||||||
|
} else {
|
||||||
|
let range = match find_all_refs(&sema, position, None) {
|
||||||
|
Some(RangeInfo { range, .. }) => range,
|
||||||
|
None => return Err(RenameError("No references found at position".to_string())),
|
||||||
|
};
|
||||||
|
Ok(RangeInfo::new(range, SourceChange::from(vec![])))
|
||||||
|
}
|
||||||
|
.map(|info| RangeInfo::new(info.range, ()))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn rename(
|
pub(crate) fn rename(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
|
@ -49,11 +72,18 @@ pub(crate) fn rename_with_semantics(
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
||||||
match lex_single_syntax_kind(new_name) {
|
let is_lifetime_name = match lex_single_syntax_kind(new_name) {
|
||||||
Some(res) => match res {
|
Some(res) => match res {
|
||||||
(SyntaxKind::IDENT, _) => (),
|
(SyntaxKind::IDENT, _) => false,
|
||||||
(SyntaxKind::UNDERSCORE, _) => (),
|
(SyntaxKind::UNDERSCORE, _) => false,
|
||||||
(SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
|
(SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
|
||||||
|
(SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true,
|
||||||
|
(SyntaxKind::LIFETIME_IDENT, _) => {
|
||||||
|
return Err(RenameError(format!(
|
||||||
|
"Invalid name `{0}`: Cannot rename lifetime to {0}",
|
||||||
|
new_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
(_, Some(syntax_error)) => {
|
(_, Some(syntax_error)) => {
|
||||||
return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
|
return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
|
||||||
}
|
}
|
||||||
|
@ -62,18 +92,21 @@ pub(crate) fn rename_with_semantics(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
|
None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
|
||||||
}
|
};
|
||||||
|
|
||||||
let source_file = sema.parse(position.file_id);
|
let source_file = sema.parse(position.file_id);
|
||||||
let syntax = source_file.syntax();
|
let syntax = source_file.syntax();
|
||||||
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
|
// this is here to prevent lifetime renames from happening on modules and self
|
||||||
|
if is_lifetime_name {
|
||||||
|
rename_reference(&sema, position, new_name, is_lifetime_name)
|
||||||
|
} else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
|
||||||
rename_mod(&sema, position, module, new_name)
|
rename_mod(&sema, position, module, new_name)
|
||||||
} else if let Some(self_token) =
|
} else if let Some(self_token) =
|
||||||
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
|
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
|
||||||
{
|
{
|
||||||
rename_self_to_param(&sema, position, self_token, new_name)
|
rename_self_to_param(&sema, position, self_token, new_name)
|
||||||
} else {
|
} else {
|
||||||
rename_reference(&sema, position, new_name)
|
rename_reference(&sema, position, new_name, is_lifetime_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,12 +388,26 @@ fn rename_reference(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
|
is_lifetime_name: bool,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
||||||
let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
|
let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
|
||||||
Some(range_info) => range_info,
|
Some(range_info) => range_info,
|
||||||
None => return Err(RenameError("No references found at position".to_string())),
|
None => return Err(RenameError("No references found at position".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) {
|
||||||
|
(true, false) => {
|
||||||
|
return Err(RenameError(format!(
|
||||||
|
"Invalid name `{}`: not a lifetime identifier",
|
||||||
|
new_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
let edit = refs
|
let edit = refs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|reference| source_edit_from_reference(sema, reference, new_name))
|
.map(|reference| source_edit_from_reference(sema, reference, new_name))
|
||||||
|
@ -464,6 +511,24 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rename_to_invalid_identifier_lifetime() {
|
||||||
|
check(
|
||||||
|
"'foo",
|
||||||
|
r#"fn main() { let i<|> = 1; }"#,
|
||||||
|
"error: Invalid name `'foo`: not an identifier",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rename_to_invalid_identifier_lifetime2() {
|
||||||
|
check(
|
||||||
|
"foo",
|
||||||
|
r#"fn main<'a>(_: &'a<|> ()) {}"#,
|
||||||
|
"error: Invalid name `foo`: not a lifetime identifier",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rename_for_local() {
|
fn test_rename_for_local() {
|
||||||
check(
|
check(
|
||||||
|
@ -1393,6 +1458,33 @@ struct Foo {
|
||||||
fn foo(Foo { i: bar }: foo) -> i32 {
|
fn foo(Foo { i: bar }: foo) -> i32 {
|
||||||
bar
|
bar
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rename_lifetimes() {
|
||||||
|
check(
|
||||||
|
"'yeeee",
|
||||||
|
r#"
|
||||||
|
trait Foo<'a> {
|
||||||
|
fn foo() -> &'a ();
|
||||||
|
}
|
||||||
|
impl<'a> Foo<'a> for &'a () {
|
||||||
|
fn foo() -> &'a<|> () {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
trait Foo<'a> {
|
||||||
|
fn foo() -> &'a ();
|
||||||
|
}
|
||||||
|
impl<'yeeee> Foo<'yeeee> for &'yeeee () {
|
||||||
|
fn foo() -> &'yeeee () {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -806,6 +806,7 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
|
||||||
}
|
}
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
Definition::LifetimeParam(_) => HighlightTag::Lifetime,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
|
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
|
||||||
|
|
||||||
use hir::{
|
use hir::{
|
||||||
db::HirDatabase, Crate, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef,
|
db::HirDatabase, Crate, Field, HasVisibility, ImplDef, LifetimeParam, Local, MacroDef, Module,
|
||||||
Name, PathResolution, Semantics, TypeParam, Visibility,
|
ModuleDef, Name, PathResolution, Semantics, TypeParam, Visibility,
|
||||||
};
|
};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode},
|
ast::{self, AstNode},
|
||||||
match_ast, SyntaxNode,
|
match_ast, SyntaxKind, SyntaxNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::RootDatabase;
|
use crate::RootDatabase;
|
||||||
|
@ -25,6 +25,8 @@ pub enum Definition {
|
||||||
SelfType(ImplDef),
|
SelfType(ImplDef),
|
||||||
Local(Local),
|
Local(Local),
|
||||||
TypeParam(TypeParam),
|
TypeParam(TypeParam),
|
||||||
|
LifetimeParam(LifetimeParam),
|
||||||
|
// FIXME: Label
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Definition {
|
impl Definition {
|
||||||
|
@ -36,6 +38,7 @@ impl Definition {
|
||||||
Definition::SelfType(it) => Some(it.module(db)),
|
Definition::SelfType(it) => Some(it.module(db)),
|
||||||
Definition::Local(it) => Some(it.module(db)),
|
Definition::Local(it) => Some(it.module(db)),
|
||||||
Definition::TypeParam(it) => Some(it.module(db)),
|
Definition::TypeParam(it) => Some(it.module(db)),
|
||||||
|
Definition::LifetimeParam(it) => Some(it.module(db)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +50,7 @@ impl Definition {
|
||||||
Definition::SelfType(_) => None,
|
Definition::SelfType(_) => None,
|
||||||
Definition::Local(_) => None,
|
Definition::Local(_) => None,
|
||||||
Definition::TypeParam(_) => None,
|
Definition::TypeParam(_) => None,
|
||||||
|
Definition::LifetimeParam(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +76,7 @@ impl Definition {
|
||||||
Definition::SelfType(_) => return None,
|
Definition::SelfType(_) => return None,
|
||||||
Definition::Local(it) => it.name(db)?,
|
Definition::Local(it) => it.name(db)?,
|
||||||
Definition::TypeParam(it) => it.name(db),
|
Definition::TypeParam(it) => it.name(db),
|
||||||
|
Definition::LifetimeParam(it) => it.name(db),
|
||||||
};
|
};
|
||||||
Some(name)
|
Some(name)
|
||||||
}
|
}
|
||||||
|
@ -229,6 +234,25 @@ impl NameClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn classify_lifetime(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
lifetime: &ast::Lifetime,
|
||||||
|
) -> Option<NameClass> {
|
||||||
|
let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string());
|
||||||
|
let parent = lifetime.syntax().parent()?;
|
||||||
|
|
||||||
|
match_ast! {
|
||||||
|
match parent {
|
||||||
|
ast::LifetimeParam(it) => {
|
||||||
|
let def = sema.to_def(&it)?;
|
||||||
|
Some(NameClass::Definition(Definition::LifetimeParam(def)))
|
||||||
|
},
|
||||||
|
ast::Label(_it) => None,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -338,6 +362,35 @@ impl NameRefClass {
|
||||||
let resolved = sema.resolve_extern_crate(&extern_crate)?;
|
let resolved = sema.resolve_extern_crate(&extern_crate)?;
|
||||||
Some(NameRefClass::ExternCrate(resolved))
|
Some(NameRefClass::ExternCrate(resolved))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn classify_lifetime(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
lifetime: &ast::Lifetime,
|
||||||
|
) -> Option<NameRefClass> {
|
||||||
|
let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string());
|
||||||
|
let parent = lifetime.syntax().parent()?;
|
||||||
|
match parent.kind() {
|
||||||
|
SyntaxKind::LIFETIME_ARG
|
||||||
|
| SyntaxKind::SELF_PARAM
|
||||||
|
| SyntaxKind::TYPE_BOUND
|
||||||
|
| SyntaxKind::WHERE_PRED
|
||||||
|
| SyntaxKind::REF_TYPE => sema
|
||||||
|
.resolve_lifetime_param(lifetime)
|
||||||
|
.map(Definition::LifetimeParam)
|
||||||
|
.map(NameRefClass::Definition),
|
||||||
|
// lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
|
||||||
|
// if our lifetime is in a LifetimeParam without being the constrained lifetime
|
||||||
|
_ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
|
||||||
|
!= Some(lifetime) =>
|
||||||
|
{
|
||||||
|
sema.resolve_lifetime_param(lifetime)
|
||||||
|
.map(Definition::LifetimeParam)
|
||||||
|
.map(NameRefClass::Definition)
|
||||||
|
}
|
||||||
|
SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => None,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PathResolution> for Definition {
|
impl From<PathResolution> for Definition {
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub enum ReferenceKind {
|
||||||
RecordFieldExprOrPat,
|
RecordFieldExprOrPat,
|
||||||
SelfKw,
|
SelfKw,
|
||||||
EnumLiteral,
|
EnumLiteral,
|
||||||
|
Lifetime,
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +130,25 @@ impl Definition {
|
||||||
return SearchScope::new(res);
|
return SearchScope::new(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Definition::LifetimeParam(param) = self {
|
||||||
|
let range = match param.parent(db) {
|
||||||
|
hir::GenericDef::Function(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::GenericDef::Adt(it) => match it {
|
||||||
|
hir::Adt::Struct(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::Adt::Union(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::Adt::Enum(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
},
|
||||||
|
hir::GenericDef::Trait(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::GenericDef::TypeAlias(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::GenericDef::ImplDef(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::GenericDef::EnumVariant(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
hir::GenericDef::Const(it) => it.source(db).value.syntax().text_range(),
|
||||||
|
};
|
||||||
|
let mut res = FxHashMap::default();
|
||||||
|
res.insert(file_id, Some(range));
|
||||||
|
return SearchScope::new(res);
|
||||||
|
}
|
||||||
|
|
||||||
let vis = self.visibility(db);
|
let vis = self.visibility(db);
|
||||||
|
|
||||||
if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) {
|
if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) {
|
||||||
|
@ -255,24 +275,41 @@ impl<'a> FindUsages<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match sema.find_node_at_offset_with_descend(&tree, offset) {
|
if let Some(name_ref) = sema.find_node_at_offset_with_descend(&tree, offset) {
|
||||||
Some(name_ref) => {
|
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} else if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
|
||||||
None => match sema.find_node_at_offset_with_descend(&tree, offset) {
|
|
||||||
Some(name) => {
|
|
||||||
if self.found_name(&name, sink) {
|
if self.found_name(&name, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} else if let Some(lifetime) = sema.find_node_at_offset_with_descend(&tree, offset)
|
||||||
None => {}
|
{
|
||||||
},
|
if self.found_lifetime(&lifetime, sink) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn found_lifetime(
|
||||||
|
&self,
|
||||||
|
lifetime: &ast::Lifetime,
|
||||||
|
sink: &mut dyn FnMut(Reference) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
match NameRefClass::classify_lifetime(self.sema, lifetime) {
|
||||||
|
Some(NameRefClass::Definition(def)) if &def == self.def => {
|
||||||
|
let reference = Reference {
|
||||||
|
file_range: self.sema.original_range(lifetime.syntax()),
|
||||||
|
kind: ReferenceKind::Lifetime,
|
||||||
|
access: None,
|
||||||
|
};
|
||||||
|
sink(reference)
|
||||||
|
}
|
||||||
|
_ => false, // not a usage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn found_name_ref(
|
fn found_name_ref(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -209,8 +209,7 @@ pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<Fil
|
||||||
query.search(&buf)
|
query.search(&buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
|
pub fn index_resolve(db: &RootDatabase, name: &SmolStr) -> Vec<FileSymbol> {
|
||||||
let name = name_ref.text();
|
|
||||||
let mut query = Query::new(name.to_string());
|
let mut query = Query::new(name.to_string());
|
||||||
query.exact();
|
query.exact();
|
||||||
query.limit(4);
|
query.limit(4);
|
||||||
|
|
|
@ -733,7 +733,7 @@ pub(crate) fn handle_prepare_rename(
|
||||||
let _p = profile::span("handle_prepare_rename");
|
let _p = profile::span("handle_prepare_rename");
|
||||||
let position = from_proto::file_position(&snap, params)?;
|
let position = from_proto::file_position(&snap, params)?;
|
||||||
|
|
||||||
let change = snap.analysis.rename(position, "dummy")??;
|
let change = snap.analysis.prepare_rename(position)??;
|
||||||
let line_index = snap.analysis.file_line_index(position.file_id)?;
|
let line_index = snap.analysis.file_line_index(position.file_id)?;
|
||||||
let range = to_proto::range(&line_index, change.range);
|
let range = to_proto::range(&line_index, change.range);
|
||||||
Ok(Some(PrepareRenameResponse::Range(range)))
|
Ok(Some(PrepareRenameResponse::Range(range)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue