Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ide-assists/src/handlers/inline_macro.rs29
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs2
-rw-r--r--crates/ide/src/expand_macro.rs17
-rw-r--r--crates/mbe/src/tests.rs2
-rw-r--r--crates/syntax-bridge/Cargo.toml1
-rw-r--r--crates/syntax-bridge/src/prettify_macro_expansion.rs359
-rw-r--r--crates/syntax/src/ast/edit.rs4
8 files changed, 354 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0b3fd2b8ea..604bc345c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2745,6 +2745,7 @@ dependencies = [
name = "syntax-bridge"
version = "0.0.0"
dependencies = [
+ "expect-test",
"intern",
"parser",
"rustc-hash 2.1.1",
diff --git a/crates/ide-assists/src/handlers/inline_macro.rs b/crates/ide-assists/src/handlers/inline_macro.rs
index 280bd7f2ca..ef9b5d093f 100644
--- a/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/crates/ide-assists/src/handlers/inline_macro.rs
@@ -1,6 +1,6 @@
use hir::db::ExpandDatabase;
use ide_db::syntax_helpers::prettify_macro_expansion;
-use syntax::ast::{self, AstNode};
+use syntax::ast::{self, AstNode, edit::AstNodeEdit};
use crate::{AssistContext, AssistId, Assists};
@@ -46,12 +46,15 @@ pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
"Inline macro".to_owned(),
text_range,
|builder| {
+ let editor = builder.make_editor(unexpanded.syntax());
let expanded = ctx.sema.parse_or_expand(macro_call.into());
let span_map = ctx.sema.db.expansion_span_map(macro_call);
// Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation,
// which can be very costly for big macros when it is done *even without the assist being invoked*.
let expanded = prettify_macro_expansion(ctx.db(), expanded, &span_map, target_crate_id);
- builder.replace(text_range, expanded.to_string())
+ let expanded = ast::edit::indent(&expanded, unexpanded.indent_level());
+ editor.replace(unexpanded.syntax(), expanded);
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
@@ -207,15 +210,15 @@ macro_rules! num {
inline_macro,
r#"
macro_rules! foo {
- () => {foo!()}
+ ($t:tt) => {foo!(1)}
}
-fn f() { let result = foo$0!(); }
+fn f() { let result = foo$0!(0); }
"#,
r#"
macro_rules! foo {
- () => {foo!()}
+ ($t:tt) => {foo!(1)}
}
-fn f() { let result = foo!(); }
+fn f() { let result = foo!(1); }
"#,
);
}
@@ -258,7 +261,7 @@ macro_rules! whitespace {
if true {}
};
}
-fn f() { if true{}; }
+fn f() { if true {}; }
"#,
)
}
@@ -297,12 +300,12 @@ macro_rules! foo {
}
fn main() {
cfg_if!{
- if #[cfg(test)]{
- 1;
- }else {
- 1;
- }
-};
+ if #[cfg(test)]{
+ 1;
+ }else {
+ 1;
+ }
+ };
}
"#,
);
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 4072f05a41..9bac34885c 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -1356,7 +1356,7 @@ noop! {
struct Test;
impl Foo for Test {
- fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> {
+ fn foo(&mut self,bar: i64,baz: &mut u32) -> Result<(),u32> {
$0
}
}
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 6ac4fa1fba..cc322d2b9e 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -357,7 +357,7 @@ fn main() {
"#,
expect![[r#"
bar!
- for _ in 0..42{}"#]],
+ for _ in 0..42 {}"#]],
);
}
@@ -433,9 +433,9 @@ fn main() {
expect![[r#"
match_ast!
{
- if let Some(it) = ast::TraitDef::cast(container.clone()){}
- else if let Some(it) = ast::ImplDef::cast(container.clone()){}
- else {
+ if let Some(it) = ast::TraitDef::cast(container.clone()){
+ }else if let Some(it) = ast::ImplDef::cast(container.clone()){
+ }else {
{
continue
}
@@ -594,7 +594,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -612,7 +612,7 @@ struct Foo {}
expect![[r#"
proc_macros::DeriveIdentity
#[derive(proc_macros::DeriveIdentity)]
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -628,7 +628,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
check(
r#"
@@ -640,7 +640,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -783,7 +783,6 @@ foo();
macro_rules! foo {
() => {
fn item(){}
-
};
}
foo();"#]],
diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs
index 4a1af31656..271dfc877b 100644
--- a/crates/mbe/src/tests.rs
+++ b/crates/mbe/src/tests.rs
@@ -237,7 +237,7 @@ fn expr_2021() {
PUNCH ; [alone] 0:Root[0000, 0]@39..40#ROOT2024
_;
- (const {
+ (const {
1
});"#]],
);
diff --git a/crates/syntax-bridge/Cargo.toml b/crates/syntax-bridge/Cargo.toml
index b0fd40ff59..c928f23e02 100644
--- a/crates/syntax-bridge/Cargo.toml
+++ b/crates/syntax-bridge/Cargo.toml
@@ -25,6 +25,7 @@ span = { path = "../span", version = "0.0.0", default-features = false}
intern.workspace = true
[dev-dependencies]
+expect-test = "1.5.1"
test-utils.workspace = true
[features]
diff --git a/crates/syntax-bridge/src/prettify_macro_expansion.rs b/crates/syntax-bridge/src/prettify_macro_expansion.rs
index 2f932e0458..678dd143f9 100644
--- a/crates/syntax-bridge/src/prettify_macro_expansion.rs
+++ b/crates/syntax-bridge/src/prettify_macro_expansion.rs
@@ -3,8 +3,7 @@ use syntax::{
NodeOrToken,
SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, T, WalkEvent,
- ast::make,
- ted::{self, Position},
+ syntax_editor::{Position, SyntaxEditor},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -29,7 +28,7 @@ pub fn prettify_macro_expansion(
let mut last: Option<SyntaxKind> = None;
let mut mods = Vec::new();
let mut dollar_crate_replacements = Vec::new();
- let syn = syn.clone_subtree().clone_for_update();
+ let (editor, syn) = SyntaxEditor::new(syn);
let before = Position::before;
let after = Position::after;
@@ -45,17 +44,21 @@ pub fn prettify_macro_expansion(
for event in syn.preorder_with_tokens() {
let token = match event {
WalkEvent::Enter(NodeOrToken::Token(token)) => token,
- WalkEvent::Leave(NodeOrToken::Node(node))
- if matches!(
- node.kind(),
- ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES
- ) =>
- {
- if indent > 0 {
+ WalkEvent::Leave(NodeOrToken::Node(node)) => {
+ let is_last_child =
+ node.parent().is_some_and(|parent| parent.last_child().as_ref() == Some(&node));
+ let is_always_newline = matches!(node.kind(), ATTR);
+ let is_non_last_newline = match node.kind() {
+ MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES | EXTERN_BLOCK
+ | EXTERN_CRATE | MODULE => true,
+ EXPR_STMT if Some(R_CURLY) == node.last_token().map(|it| it.kind()) => true,
+ _ => false,
+ };
+ if (!is_last_child && is_non_last_newline) || is_always_newline {
mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent)));
- }
- if node.parent().is_some() {
- mods.push((Position::after(node), PrettifyWsKind::Newline));
+ if node.parent().is_some() {
+ mods.push((Position::after(node), PrettifyWsKind::Newline));
+ }
}
continue;
}
@@ -77,44 +80,36 @@ pub fn prettify_macro_expansion(
match tok.kind() {
k if is_text(k)
- && is_next(|it| !it.is_punct() || matches!(it, T![_] | T![#]), false) =>
+ && is_next(|it| !it.is_punct() || matches!(it, T![_] | T![#] | L_CURLY), false) =>
{
mods.push(do_ws(after, tok));
}
L_CURLY if is_next(|it| it != R_CURLY, true) => {
indent += 1;
- if is_last(is_text, false) {
- mods.push(do_ws(before, tok));
- }
-
mods.push(do_indent(after, tok, indent));
mods.push(do_nl(after, tok));
}
R_CURLY if is_last(|it| it != L_CURLY, true) => {
indent = indent.saturating_sub(1);
- if indent > 0 {
- mods.push(do_indent(before, tok, indent));
- }
+ mods.push(do_indent(before, tok, indent));
mods.push(do_nl(before, tok));
}
- R_CURLY => {
- if indent > 0 {
- mods.push(do_indent(after, tok, indent));
- }
- mods.push(do_nl(after, tok));
+ R_CURLY if is_next(|it| it == T![else], false) => {
+ mods.push(do_indent(before, tok, indent));
+ mods.push(do_nl(before, tok));
}
LIFETIME_IDENT if is_next(is_text, true) => {
mods.push(do_ws(after, tok));
}
- AS_KW | DYN_KW | IMPL_KW | CONST_KW | MUT_KW => {
+ AS_KW | DYN_KW | IMPL_KW | CONST_KW | MUT_KW | LET_KW | MATCH_KW => {
mods.push(do_ws(after, tok));
}
T![;] if is_next(|it| it != R_CURLY, true) => {
- if indent > 0 {
- mods.push(do_indent(after, tok, indent));
+ mods.push(do_indent(after, tok, indent));
+ if tok.text_range().end() != syn.text_range().end() {
+ mods.push(do_nl(after, tok));
}
- mods.push(do_nl(after, tok));
}
T![=] if is_next(|it| it == T![>], false) => {
// FIXME: this branch is for `=>` in macro_rules!, which is currently parsed as
@@ -126,6 +121,12 @@ pub fn prettify_macro_expansion(
mods.push(do_ws(before, tok));
mods.push(do_ws(after, tok));
}
+ T![:] if is_next(|it| it != T![:], false) && is_last(|it| it != T![:], false) => {
+ // XXX: Why input included WHITESPACE?
+ if is_next(|it| it != SyntaxKind::WHITESPACE, false) {
+ mods.push(do_ws(after, tok));
+ }
+ }
T![!] if is_last(|it| it == MACRO_RULES_KW, false) && is_next(is_text, false) => {
mods.push(do_ws(after, tok));
}
@@ -137,27 +138,311 @@ pub fn prettify_macro_expansion(
inspect_mods(&mods);
for (pos, insert) in mods {
- ted::insert_raw(
+ editor.insert(
pos,
match insert {
- PrettifyWsKind::Space => make::tokens::single_space(),
- PrettifyWsKind::Indent(indent) => make::tokens::whitespace(&" ".repeat(4 * indent)),
- PrettifyWsKind::Newline => make::tokens::single_newline(),
+ PrettifyWsKind::Space => editor.make().whitespace(" "),
+ PrettifyWsKind::Indent(0) => continue,
+ PrettifyWsKind::Indent(indent) => editor.make().whitespace(&" ".repeat(4 * indent)),
+ PrettifyWsKind::Newline => editor.make().whitespace("\n"),
},
);
}
for (old, new) in dollar_crate_replacements {
- ted::replace(old, new);
+ editor.replace(old, new);
}
if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
- ted::remove(it);
+ editor.delete(it);
}
- syn
+ editor.finish().new_root().clone()
}
fn is_text(k: SyntaxKind) -> bool {
// Consider all keywords in all editions.
k.is_any_identifier() || k.is_literal() || k == UNDERSCORE
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use expect_test::{Expect, expect};
+
+ #[expect(deprecated)]
+ fn check_pretty(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
+ let ra_fixture = stdx::trim_indent(ra_fixture);
+ let source_file = syntax::ast::SourceFile::parse(&ra_fixture, span::Edition::CURRENT);
+ let syn = remove_whitespaces(&source_file.syntax_node());
+
+ let pretty = prettify_macro_expansion(syn, &mut |_| None, |_| ());
+ let mut pretty = pretty.to_string();
+ if pretty.contains('\n') {
+ pretty.push('\n');
+ }
+ expect.assert_eq(&pretty);
+
+ fn remove_whitespaces(node: &SyntaxNode) -> SyntaxNode {
+ let (editor, node) = SyntaxEditor::new(node.clone());
+ node.preorder_with_tokens().for_each(|it| match it {
+ WalkEvent::Enter(NodeOrToken::Token(tok)) if tok.kind().is_trivia() => {
+ editor.delete(tok);
+ }
+ _ => (),
+ });
+ editor.finish().new_root().clone()
+ }
+ }
+
+ #[test]
+ fn test_colon() {
+ check_pretty(
+ r#"
+ const X: i32 = x::y::z;
+ macro_rules! foo {
+ () => {
+ $crate::foo::bar!()
+ };
+ }
+ "#,
+ expect![[r#"
+ const X: i32 = x::y::z;
+ macro_rules! foo {
+ () => {
+ $crate::foo::bar!()
+ };
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_curly_indent() {
+ check_pretty(
+ r#"
+ const _: () = {
+ {
+ 2;
+ 3
+ }
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ {
+ 2;
+ 3
+ }
+ };
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_pats() {
+ check_pretty(
+ r#"
+ const _: () = {
+ let x = 2;
+ let mut y = 3;
+ let ref mut z @ 0..5 = 4;
+ let (x, ref y) = (5, 6);
+ let (Foo { x, y }, Bar(z, t));
+ let (&mut x, (y | y));
+ match () {}
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ let x = 2;
+ let mut y = 3;
+ let ref mut [email protected] = 4;
+ let (x,ref y) = (5,6);
+ let (Foo {
+ x,y
+ },Bar(z,t));
+ let (&mut x,(y|y));
+ match (){}
+ };
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_attrs() {
+ check_pretty(
+ r#"
+ #[attr1]
+ #[attr2]
+ const _: () = {};
+ #[attr1]
+ const _: () = {
+ #[attr2]
+ {}
+ };
+ "#,
+ expect![[r#"
+ #[attr1]
+ #[attr2]
+ const _: () = {};
+ #[attr1]
+ const _: () = {
+ #[attr2]
+ {}
+ };
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_items() {
+ check_pretty(
+ r#"
+ fn foo() {}
+ struct Foo {}
+ struct Foo;
+ enum Foo {}
+ impl Foo {}
+ const _: () = {};
+ static S: () = {};
+ extern {}
+ mod x {}
+ mod x;
+ type X = 2;
+ use a;
+ use b::{c, d};
+ macro_rules! foo { () => {}; }
+ "#,
+ expect![[r#"
+ fn foo(){}
+ struct Foo {}
+ struct Foo;
+
+ enum Foo {}
+ impl Foo {}
+ const _: () = {};
+ static S: () = {};
+ extern {}
+ mod x {}
+ mod x;
+
+ type X = 2;
+ use a;
+ use b::{
+ c,d
+ };
+ macro_rules! foo {
+ () => {};
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_exprs() {
+ check_pretty(
+ r#"
+ const _: () = {
+ let _ = 1+2;
+ let _ = !true && false;
+ let _ = foo() + !bar() + dbg!(2) + *x;
+ let _ = async move || {};
+ let _ = async move {};
+ let _ = x.await;
+ 'lab: for _ in 0..5 {
+ loop { }
+ break 'lab expr;
+ if let pat = expr {
+ foo()
+ } else if true {
+ bar()
+ } else {}
+ if true {} else if true {} else {}
+ fun()
+ }
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ let _ = 1+2;
+ let _ = !true&&false;
+ let _ = foo()+!bar()+dbg!(2)+*x;
+ let _ = async move||{};
+ let _ = async move {};
+ let _ = x.await;
+ 'lab: for _ in 0..5 {
+ loop {}
+ break 'lab expr;
+ if let pat = expr {
+ foo()
+ }else if true {
+ bar()
+ }else {}
+ if true {
+ }else if true {
+ }else {}
+ fun()
+ }
+ };
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_match_arm() {
+ check_pretty(
+ r#"
+ const _: () = {
+ match 2 {
+ tmp => foo!(),
+ };
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ match 2 {
+ tmp => foo!(),
+ };
+ };
+ "#]],
+ );
+
+ check_pretty(
+ r#"
+ const _: () = {
+ match 2 {
+ tmp => {}
+ };
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ match 2 {
+ tmp => {}
+ };
+ };
+ "#]],
+ );
+
+ check_pretty(
+ r#"
+ const _: () = {
+ match 2 {
+ 1 => {}
+ 2 => foo(),
+ _ => {},
+ };
+ };
+ "#,
+ expect![[r#"
+ const _: () = {
+ match 2 {
+ 1 => {}
+ 2 => foo(),
+ _ => {},
+ };
+ };
+ "#]],
+ );
+ }
+}
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index b20aa90d06..0ffcbd212b 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -252,6 +252,10 @@ impl ast::IdentPat {
}
}
+pub fn indent(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
+ level.clone_increase_indent(node)
+}
+
#[test]
fn test_increase_indent() {
let arm_list = {