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())
},
|_| (),
)
}

View file

@ -74,7 +74,8 @@ fn check_(
"{}",
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
node.syntax_node(),
&mut |it| it.clone()
&mut |_| None,
|_| ()
)
);
expect.assert_eq(&expect_res);

View file

@ -112,7 +112,10 @@ pub struct EditionedFileId(u32);
impl fmt::Debug for EditionedFileId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("EditionedFileId").field(&self.file_id()).field(&self.edition()).finish()
f.debug_tuple("EditionedFileId")
.field(&self.file_id().index())
.field(&self.edition())
.finish()
}
}

View file

@ -7,6 +7,13 @@ use syntax::{
ted::{self, Position},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrettifyWsKind {
Space,
Indent(usize),
Newline,
}
/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
///
/// This is an internal API that is only exported because `mbe` needs it for tests and cannot depend
@ -15,7 +22,8 @@ use syntax::{
#[deprecated = "use `hir_expand::prettify_macro_expansion()` instead"]
pub fn prettify_macro_expansion(
syn: SyntaxNode,
dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> SyntaxToken,
dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> Option<SyntaxToken>,
inspect_mods: impl FnOnce(&[(Position, PrettifyWsKind)]),
) -> SyntaxNode {
let mut indent = 0;
let mut last: Option<SyntaxKind> = None;
@ -27,14 +35,12 @@ pub fn prettify_macro_expansion(
let after = Position::after;
let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
(pos(token.clone()), make::tokens::whitespace(&" ".repeat(4 * indent)))
};
let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
(pos(token.clone()), make::tokens::single_space())
};
let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
(pos(token.clone()), make::tokens::single_newline())
(pos(token.clone()), PrettifyWsKind::Indent(indent))
};
let do_ws =
|pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Space);
let do_nl =
|pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Newline);
for event in syn.preorder_with_tokens() {
let token = match event {
@ -46,20 +52,19 @@ pub fn prettify_macro_expansion(
) =>
{
if indent > 0 {
mods.push((
Position::after(node.clone()),
make::tokens::whitespace(&" ".repeat(4 * indent)),
));
mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent)));
}
if node.parent().is_some() {
mods.push((Position::after(node), make::tokens::single_newline()));
mods.push((Position::after(node), PrettifyWsKind::Newline));
}
continue;
}
_ => continue,
};
if token.kind() == SyntaxKind::IDENT && token.text() == "$crate" {
dollar_crate_replacements.push((token.clone(), dollar_crate_replacement(&token)));
if let Some(replacement) = dollar_crate_replacement(&token) {
dollar_crate_replacements.push((token.clone(), replacement));
}
}
let tok = &token;
@ -129,8 +134,16 @@ pub fn prettify_macro_expansion(
last = Some(tok.kind());
}
inspect_mods(&mods);
for (pos, insert) in mods {
ted::insert(pos, insert);
ted::insert_raw(
pos,
match insert {
PrettifyWsKind::Space => make::tokens::single_space(),
PrettifyWsKind::Indent(indent) => make::tokens::whitespace(&" ".repeat(4 * indent)),
PrettifyWsKind::Newline => make::tokens::single_newline(),
},
);
}
for (old, new) in dollar_crate_replacements {
ted::replace(old, new);