Add some more hir_expand::files conversions

This commit is contained in:
Lukas Wirth 2025-05-30 14:38:42 +02:00
parent e65dddaf59
commit f0e39c77cc
6 changed files with 137 additions and 64 deletions

View file

@ -42,6 +42,49 @@ impl FilePosition {
FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset }
}
}
impl From<FileRange> for HirFileRange {
fn from(value: FileRange) -> Self {
HirFileRange { file_id: value.file_id.into(), range: value.range }
}
}
impl From<FilePosition> for HirFilePosition {
fn from(value: FilePosition) -> Self {
HirFilePosition { file_id: value.file_id.into(), offset: value.offset }
}
}
impl FilePositionWrapper<span::FileId> {
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FilePosition {
FilePositionWrapper {
file_id: EditionedFileId::new(db, self.file_id, edition),
offset: self.offset,
}
}
}
impl FileRangeWrapper<span::FileId> {
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FileRange {
FileRangeWrapper {
file_id: EditionedFileId::new(db, self.file_id, edition),
range: self.range,
}
}
}
impl<T> InFileWrapper<span::FileId, T> {
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile<T> {
InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value }
}
}
impl HirFileRange {
pub fn file_range(self) -> Option<FileRange> {
Some(FileRange { file_id: self.file_id.file_id()?, range: self.range })
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct FileRangeWrapper<FileKind> {
pub file_id: FileKind,
@ -194,6 +237,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
self.with_value(self.value.syntax())
}
pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
}
}
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
@ -204,9 +250,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
}
// region:specific impls
impl<SN: Borrow<SyntaxNode>> InRealFile<SN> {
pub fn file_range(&self) -> FileRange {
FileRange { file_id: self.file_id, range: self.value.borrow().text_range() }
impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
pub fn file_range(&self) -> FileRangeWrapper<FileId> {
FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
}
}

View file

@ -392,6 +392,10 @@ impl HirFileId {
}
}
pub fn call_node(self, db: &dyn ExpandDatabase) -> Option<InFile<SyntaxNode>> {
Some(db.lookup_intern_macro_call(self.macro_file()?).to_node(db))
}
pub fn as_builtin_derive_attr_node(
&self,
db: &dyn ExpandDatabase,
@ -848,7 +852,10 @@ impl ExpansionInfo {
map_node_range_up(db, &self.exp_map, range)
}
/// Maps up the text range out of the expansion into is macro call.
/// Maps up the text range out of the expansion into its macro call.
///
/// Note that this may return multiple ranges as we lose the precise association between input to output
/// and as such we may consider inputs that are unrelated.
pub fn map_range_up_once(
&self,
db: &dyn ExpandDatabase,
@ -864,11 +871,10 @@ impl ExpansionInfo {
InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] }
}
SpanMap::ExpansionSpanMap(arg_map) => {
let arg_range = self
.arg
.value
.as_ref()
.map_or_else(|| TextRange::empty(TextSize::from(0)), |it| it.text_range());
let Some(arg_node) = &self.arg.value else {
return InFile::new(self.arg.file_id, smallvec::smallvec![]);
};
let arg_range = arg_node.text_range();
InFile::new(
self.arg.file_id,
arg_map

View file

@ -20,42 +20,46 @@ pub fn prettify_macro_expansion(
let span_offset = syn.text_range().start();
let target_crate = target_crate_id.data(db);
let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| {
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
let replacement =
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
let macro_call_id =
ctx.outer_expn(db).expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
let macro_call = db.lookup_intern_macro_call(macro_call_id.into());
let macro_def_crate = macro_call.def.krate;
// First, if this is the same crate as the macro, nothing will work but `crate`.
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
// will work in inserted code and match the user's expectation.
// If not, the crate's display name is what the dependency name is likely to be once such dependency
// is inserted, and also understandable to the user.
// Lastly, if nothing else found, resort to leaving `$crate`.
if target_crate_id == macro_def_crate {
make::tokens::crate_kw()
} else if let Some(dep) =
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
{
make::tokens::ident(dep.name.as_str())
} else if let Some(crate_name) = &macro_def_crate.extra_data(db).display_name {
make::tokens::ident(crate_name.crate_name().as_str())
} else {
return dollar_crate.clone();
}
});
if replacement.text() == "$crate" {
// The parent may have many children, and looking for the token may yield incorrect results.
return dollar_crate.clone();
}
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
parent
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|it| it.kind() == replacement.kind())
.unwrap()
})
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
syn,
&mut |dollar_crate| {
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
let replacement =
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
let macro_call_id = ctx
.outer_expn(db)
.expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
let macro_call = db.lookup_intern_macro_call(macro_call_id.into());
let macro_def_crate = macro_call.def.krate;
// First, if this is the same crate as the macro, nothing will work but `crate`.
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
// will work in inserted code and match the user's expectation.
// If not, the crate's display name is what the dependency name is likely to be once such dependency
// is inserted, and also understandable to the user.
// Lastly, if nothing else found, resort to leaving `$crate`.
if target_crate_id == macro_def_crate {
make::tokens::crate_kw()
} else if let Some(dep) =
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
{
make::tokens::ident(dep.name.as_str())
} else if let Some(crate_name) = &macro_def_crate.extra_data(db).display_name {
make::tokens::ident(crate_name.crate_name().as_str())
} else {
return dollar_crate.clone();
}
});
if replacement.text() == "$crate" {
// The parent may have many children, and looking for the token may yield incorrect results.
return None;
}
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
parent
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|it| it.kind() == replacement.kind())
},
|_| (),
)
}