diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index 321ee8feb9..8024823cbc 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -42,6 +42,49 @@ impl FilePosition { FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset } } } + +impl From for HirFileRange { + fn from(value: FileRange) -> Self { + HirFileRange { file_id: value.file_id.into(), range: value.range } + } +} + +impl From for HirFilePosition { + fn from(value: FilePosition) -> Self { + HirFilePosition { file_id: value.file_id.into(), offset: value.offset } + } +} + +impl FilePositionWrapper { + 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 { + 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 InFileWrapper { + pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile { + InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value } + } +} + +impl HirFileRange { + pub fn file_range(self) -> Option { + Some(FileRange { file_id: self.file_id.file_id()?, range: self.range }) + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct FileRangeWrapper { pub file_id: FileKind, @@ -194,6 +237,9 @@ impl InFileWrapper { pub fn syntax(&self) -> InFileWrapper { self.with_value(self.value.syntax()) } + pub fn node_file_range(&self) -> FileRangeWrapper { + FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() } + } } impl InFileWrapper { @@ -204,9 +250,9 @@ impl InFileWrapper { } // region:specific impls -impl> InRealFile { - pub fn file_range(&self) -> FileRange { - FileRange { file_id: self.file_id, range: self.value.borrow().text_range() } +impl> InFileWrapper { + pub fn file_range(&self) -> FileRangeWrapper { + FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() } } } diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index d844d8f41e..6ecac1463f 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -392,6 +392,10 @@ impl HirFileId { } } + pub fn call_node(self, db: &dyn ExpandDatabase) -> Option> { + 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 diff --git a/crates/hir-expand/src/prettify_macro_expansion_.rs b/crates/hir-expand/src/prettify_macro_expansion_.rs index 11cc434c2d..6134c3a36b 100644 --- a/crates/hir-expand/src/prettify_macro_expansion_.rs +++ b/crates/hir-expand/src/prettify_macro_expansion_.rs @@ -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) = ¯o_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) = ¯o_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()) + }, + |_| (), + ) } diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs index 3369dfff28..769455faac 100644 --- a/crates/mbe/src/tests.rs +++ b/crates/mbe/src/tests.rs @@ -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); diff --git a/crates/span/src/lib.rs b/crates/span/src/lib.rs index 54f90908f3..f81648ac42 100644 --- a/crates/span/src/lib.rs +++ b/crates/span/src/lib.rs @@ -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() } } diff --git a/crates/syntax-bridge/src/prettify_macro_expansion.rs b/crates/syntax-bridge/src/prettify_macro_expansion.rs index e815e07d80..0a5c8df0d0 100644 --- a/crates/syntax-bridge/src/prettify_macro_expansion.rs +++ b/crates/syntax-bridge/src/prettify_macro_expansion.rs @@ -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, + inspect_mods: impl FnOnce(&[(Position, PrettifyWsKind)]), ) -> SyntaxNode { let mut indent = 0; let mut last: Option = 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);