mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-26 11:59:49 +00:00
Implicit format args support
This commit is contained in:
parent
5b8e386bae
commit
d2cd30007c
37 changed files with 615 additions and 174 deletions
|
@ -117,7 +117,7 @@ pub fn get_definition(
|
|||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: SyntaxToken,
|
||||
) -> Option<Definition> {
|
||||
for token in sema.descend_into_macros(DescendPreference::None, token, 0.into()) {
|
||||
for token in sema.descend_into_macros(DescendPreference::None, token) {
|
||||
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
|
||||
if let Some(&[x]) = def.as_deref() {
|
||||
return Some(x);
|
||||
|
|
|
@ -34,7 +34,7 @@ use text_edit::{TextEdit, TextEditBuilder};
|
|||
|
||||
use crate::{
|
||||
defs::Definition,
|
||||
search::FileReference,
|
||||
search::{FileReference, FileReferenceNode},
|
||||
source_change::{FileSystemEdit, SourceChange},
|
||||
syntax_helpers::node_ext::expr_as_name_ref,
|
||||
traits::convert_to_def_in_trait,
|
||||
|
@ -361,7 +361,7 @@ pub fn source_edit_from_references(
|
|||
// macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
|
||||
let mut edited_ranges = Vec::new();
|
||||
for &FileReference { range, ref name, .. } in references {
|
||||
let name_range = name.syntax().text_range();
|
||||
let name_range = name.text_range();
|
||||
if name_range.len() != range.len() {
|
||||
// This usage comes from a different token kind that was downmapped to a NameLike in a macro
|
||||
// Renaming this will most likely break things syntax-wise
|
||||
|
@ -371,17 +371,17 @@ pub fn source_edit_from_references(
|
|||
// if the ranges differ then the node is inside a macro call, we can't really attempt
|
||||
// to make special rewrites like shorthand syntax and such, so just rename the node in
|
||||
// the macro input
|
||||
ast::NameLike::NameRef(name_ref) if name_range == range => {
|
||||
FileReferenceNode::NameRef(name_ref) if name_range == range => {
|
||||
source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
|
||||
}
|
||||
ast::NameLike::Name(name) if name_range == range => {
|
||||
FileReferenceNode::Name(name) if name_range == range => {
|
||||
source_edit_from_name(&mut edit, name, new_name)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !has_emitted_edit && !edited_ranges.contains(&range.start()) {
|
||||
let (range, new_name) = match name {
|
||||
ast::NameLike::Lifetime(_) => (
|
||||
FileReferenceNode::Lifetime(_) => (
|
||||
TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
|
||||
new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
|
||||
),
|
||||
|
|
|
@ -9,13 +9,13 @@ use std::mem;
|
|||
use base_db::{salsa::Database, FileId, FileRange, SourceDatabase, SourceDatabaseExt};
|
||||
use hir::{
|
||||
AsAssocItem, DefWithBody, DescendPreference, HasAttrs, HasSource, HirFileIdExt, InFile,
|
||||
InRealFile, ModuleSource, Semantics, Visibility,
|
||||
InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
|
||||
};
|
||||
use memchr::memmem::Finder;
|
||||
use nohash_hasher::IntMap;
|
||||
use once_cell::unsync::Lazy;
|
||||
use parser::SyntaxKind;
|
||||
use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
|
||||
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxElement, TextRange, TextSize};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
|
@ -63,10 +63,67 @@ pub struct FileReference {
|
|||
/// The range of the reference in the original file
|
||||
pub range: TextRange,
|
||||
/// The node of the reference in the (macro-)file
|
||||
pub name: ast::NameLike,
|
||||
pub name: FileReferenceNode,
|
||||
pub category: Option<ReferenceCategory>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileReferenceNode {
|
||||
Name(ast::Name),
|
||||
NameRef(ast::NameRef),
|
||||
Lifetime(ast::Lifetime),
|
||||
FormatStringEntry(ast::String, TextRange),
|
||||
}
|
||||
|
||||
impl FileReferenceNode {
|
||||
pub fn text_range(&self) -> TextRange {
|
||||
match self {
|
||||
FileReferenceNode::Name(it) => it.syntax().text_range(),
|
||||
FileReferenceNode::NameRef(it) => it.syntax().text_range(),
|
||||
FileReferenceNode::Lifetime(it) => it.syntax().text_range(),
|
||||
FileReferenceNode::FormatStringEntry(_, range) => *range,
|
||||
}
|
||||
}
|
||||
pub fn syntax(&self) -> SyntaxElement {
|
||||
match self {
|
||||
FileReferenceNode::Name(it) => it.syntax().clone().into(),
|
||||
FileReferenceNode::NameRef(it) => it.syntax().clone().into(),
|
||||
FileReferenceNode::Lifetime(it) => it.syntax().clone().into(),
|
||||
FileReferenceNode::FormatStringEntry(it, _) => it.syntax().clone().into(),
|
||||
}
|
||||
}
|
||||
pub fn into_name_like(self) -> Option<ast::NameLike> {
|
||||
match self {
|
||||
FileReferenceNode::Name(it) => Some(ast::NameLike::Name(it)),
|
||||
FileReferenceNode::NameRef(it) => Some(ast::NameLike::NameRef(it)),
|
||||
FileReferenceNode::Lifetime(it) => Some(ast::NameLike::Lifetime(it)),
|
||||
FileReferenceNode::FormatStringEntry(_, _) => None,
|
||||
}
|
||||
}
|
||||
pub fn as_name_ref(&self) -> Option<&ast::NameRef> {
|
||||
match self {
|
||||
FileReferenceNode::NameRef(name_ref) => Some(name_ref),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn as_lifetime(&self) -> Option<&ast::Lifetime> {
|
||||
match self {
|
||||
FileReferenceNode::Lifetime(lifetime) => Some(lifetime),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn text(&self) -> syntax::TokenText<'_> {
|
||||
match self {
|
||||
FileReferenceNode::NameRef(name_ref) => name_ref.text(),
|
||||
FileReferenceNode::Name(name) => name.text(),
|
||||
FileReferenceNode::Lifetime(lifetime) => lifetime.text(),
|
||||
FileReferenceNode::FormatStringEntry(it, range) => {
|
||||
syntax::TokenText::borrowed(&it.text()[*range - it.syntax().text_range().start()])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ReferenceCategory {
|
||||
// FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
|
||||
|
@ -467,7 +524,7 @@ impl<'a> FindUsages<'a> {
|
|||
// every textual hit. That function is notoriously
|
||||
// expensive even for things that do not get down mapped
|
||||
// into macros.
|
||||
sema.descend_into_macros(DescendPreference::None, token, offset)
|
||||
sema.descend_into_macros(DescendPreference::None, token)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.parent())
|
||||
})
|
||||
|
@ -479,6 +536,17 @@ impl<'a> FindUsages<'a> {
|
|||
|
||||
// Search for occurrences of the items name
|
||||
for offset in match_indices(&text, finder, search_range) {
|
||||
tree.token_at_offset(offset).into_iter().for_each(|token| {
|
||||
let Some(str_token) = ast::String::cast(token.clone()) else { return };
|
||||
if let Some((range, nameres)) =
|
||||
sema.check_for_format_args_template(token.clone(), offset)
|
||||
{
|
||||
if self.found_format_args_ref(file_id, range, str_token, nameres, sink) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) {
|
||||
if match name {
|
||||
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
|
||||
|
@ -593,7 +661,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: None,
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -612,7 +680,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: is_name_ref_in_import(name_ref).then_some(ReferenceCategory::Import),
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -621,6 +689,27 @@ impl<'a> FindUsages<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn found_format_args_ref(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
range: TextRange,
|
||||
token: ast::String,
|
||||
res: Option<PathResolution>,
|
||||
sink: &mut dyn FnMut(FileId, FileReference) -> bool,
|
||||
) -> bool {
|
||||
match res.map(Definition::from) {
|
||||
Some(def) if def == self.def => {
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: FileReferenceNode::FormatStringEntry(token, range),
|
||||
category: Some(ReferenceCategory::Read),
|
||||
};
|
||||
sink(file_id, reference)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn found_lifetime(
|
||||
&self,
|
||||
lifetime: &ast::Lifetime,
|
||||
|
@ -631,7 +720,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::Lifetime(lifetime.clone()),
|
||||
name: FileReferenceNode::Lifetime(lifetime.clone()),
|
||||
category: None,
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -655,7 +744,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: ReferenceCategory::new(&def, name_ref),
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -671,7 +760,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: ReferenceCategory::new(&def, name_ref),
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -681,7 +770,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: ReferenceCategory::new(&def, name_ref),
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -705,7 +794,7 @@ impl<'a> FindUsages<'a> {
|
|||
};
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||
name: FileReferenceNode::NameRef(name_ref.clone()),
|
||||
category: access,
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -728,7 +817,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::Name(name.clone()),
|
||||
name: FileReferenceNode::Name(name.clone()),
|
||||
// FIXME: mutable patterns should have `Write` access
|
||||
category: Some(ReferenceCategory::Read),
|
||||
};
|
||||
|
@ -738,7 +827,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::Name(name.clone()),
|
||||
name: FileReferenceNode::Name(name.clone()),
|
||||
category: None,
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
@ -763,7 +852,7 @@ impl<'a> FindUsages<'a> {
|
|||
let FileRange { file_id, range } = self.sema.original_range(name.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
name: ast::NameLike::Name(name.clone()),
|
||||
name: FileReferenceNode::Name(name.clone()),
|
||||
category: None,
|
||||
};
|
||||
sink(file_id, reference)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue