Implicit format args support

This commit is contained in:
Lukas Wirth 2023-12-05 15:42:39 +01:00
parent 5b8e386bae
commit d2cd30007c
37 changed files with 615 additions and 174 deletions

View file

@ -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);

View file

@ -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(),
),

View file

@ -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)