diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs index 6c9808fb1c3b..6fe16745063d 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -10,7 +10,7 @@ use syntax::{ use crate::{ AssistContext, AssistId, AssistKind, Assists, GroupLabel, - utils::{convert_param_list_to_arg_list, find_struct_impl}, + utils::{convert_param_list_to_arg_list, find_struct_impl, pretty_node_inside_macro}, }; // Assist: generate_delegate_methods @@ -121,6 +121,8 @@ pub(crate) fn generate_delegate_methods( let source_scope = ctx.sema.scope(v.syntax()); let target_scope = ctx.sema.scope(strukt.syntax()); if let (Some(s), Some(t)) = (source_scope, target_scope) { + let v = + pretty_node_inside_macro(v, &ctx.sema, source.file_id, t.krate()); ast::Fn::cast( PathTransform::generic_transformation(&t, &s).apply(v.syntax()), ) @@ -669,7 +671,7 @@ impl<'a, T> Bar<'a, T> { //- /bar.rs macro_rules! test_method { () => { - pub fn test(self, b: Bar) -> Self { + pub fn test(self, b: &mut Bar) -> Self { self } }; @@ -696,7 +698,7 @@ struct Foo { } impl Foo { - $0pub fn test(self,b:bar::Bar) ->bar::Bar { + $0pub fn test(self,b: &mut bar::Bar) -> bar::Bar { self.bar.test(b) } } diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs index e21f1ab35984..5dccd6b48bb2 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -2,7 +2,7 @@ use std::ops::Not; use crate::{ assist_context::{AssistContext, Assists}, - utils::convert_param_list_to_arg_list, + utils::{convert_param_list_to_arg_list, pretty_node_inside_macro}, }; use either::Either; use hir::{HasVisibility, db::HirDatabase}; @@ -243,7 +243,7 @@ impl Struct { |builder| { builder.insert( self.strukt.syntax().text_range().end(), - format!("\n\n{}", delegate.syntax()), + format!("\n\n{}", delegate().syntax()), ); }, ); @@ -251,14 +251,14 @@ impl Struct { } } -fn generate_impl( - ctx: &AssistContext<'_, '_>, - strukt: &Struct, - field_ty: &ast::Type, - field_name: &str, - delegee: &Delegee, +fn generate_impl<'a>( + ctx: &'a AssistContext<'_, '_>, + strukt: &'a Struct, + field_ty: &'a ast::Type, + field_name: &'a str, + delegee: &'a Delegee, edition: Edition, -) -> Option { +) -> Option ast::Impl + 'a>> { let make = SyntaxFactory::without_mappings(); let db = ctx.db(); let ast_strukt = &strukt.strukt; @@ -268,177 +268,211 @@ fn generate_impl( match delegee { Delegee::Bound(delegee) => { let bound_def = ctx.sema.source(delegee.to_owned())?.value; - let bound_params = bound_def.generic_param_list(); - - let delegate = make.impl_trait( - None, - delegee.is_unsafe(db), - bound_params.clone(), - bound_params.map(|params| params.to_generic_args(&make)), - strukt_params.clone(), - strukt_params.map(|params| params.to_generic_args(&make)), - delegee.is_auto(db), - make.ty(&delegee.name(db).display_no_db(edition).to_smolstr()), - strukt_ty, - bound_def.where_clause(), - ast_strukt.where_clause(), - None, - ); - - // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths - let qualified_path_type = - make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); - - // Collect assoc items - let assoc_items: Option> = bound_def.assoc_item_list().map(|ai| { - ai.assoc_items() - .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) - .filter_map(|item| { - process_assoc_item(item, qualified_path_type.clone(), field_name) - }) - .collect() - }); + let source_scope = ctx.sema.scope(bound_def.syntax())?; + let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; - let delegate = finalize_delegate(&make, &delegate, assoc_items, false)?; + Some(Box::new(move || { + let bound_def = pretty_node_inside_macro( + bound_def, + &ctx.sema, + source_scope.file_id(), + target_scope.krate(), + ); + let bound_params = bound_def.generic_param_list(); + let delegate = make.impl_trait( + None, + delegee.is_unsafe(db), + bound_params.clone(), + bound_params.map(|params| params.to_generic_args(&make)), + strukt_params.clone(), + strukt_params.map(|params| params.to_generic_args(&make)), + delegee.is_auto(db), + make.ty(&delegee.name(db).display_no_db(edition).to_smolstr()), + strukt_ty, + bound_def.where_clause(), + ast_strukt.where_clause(), + None, + ); - let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; - let source_scope = ctx.sema.scope(bound_def.syntax())?; - let transform = PathTransform::generic_transformation(&target_scope, &source_scope); - ast::Impl::cast(transform.apply(delegate.syntax())) + // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths + let qualified_path_type = make.path_from_text(&format!( + "<{} as {}>", + field_ty, + delegate.trait_().unwrap() + )); + + // Collect assoc items + let assoc_items: Option> = + bound_def.assoc_item_list().map(|ai| { + ai.assoc_items() + .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) + .filter_map(|item| { + process_assoc_item(item, qualified_path_type.clone(), field_name) + }) + .collect() + }); + + let delegate = finalize_delegate(&make, &delegate, assoc_items, false); + + let transform = PathTransform::generic_transformation(&target_scope, &source_scope); + ast::Impl::cast(transform.apply(delegate.syntax())).unwrap() + })) } Delegee::Impls(trait_, old_impl) => { let old_impl = ctx.sema.source(old_impl.to_owned())?.value; let old_impl_params = old_impl.generic_param_list(); + let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?; + let target_scope = ctx.sema.scope(ast_strukt.syntax())?; + let hir_old_impl = ctx.sema.to_impl_def(&old_impl)?; + + let _old_trait = old_impl.trait_()?; + let _old_self_ty = old_impl.self_ty()?; + + Some(Box::new(move || { + let old_impl = pretty_node_inside_macro( + old_impl, + &ctx.sema, + source_scope.file_id(), + target_scope.krate(), + ); + let old_trait = old_impl.trait_().unwrap(); + let old_self_ty = old_impl.self_ty().unwrap(); + + // 1) Resolve conflicts between generic parameters in old_impl and + // those in strukt. + // + // These generics parameters will also be used in `field_ty` and + // `where_clauses`, so we should substitute arguments in them as well. + let strukt_params = resolve_name_conflicts(strukt_params, &old_impl_params); + let (field_ty, ty_where_clause) = match &strukt_params { + Some(strukt_params) => { + let args = strukt_params.to_generic_args(&make); + let field_ty = rename_strukt_args(ctx, ast_strukt, field_ty, &args) + .unwrap_or_else(|| field_ty.clone()); + let where_clause = ast_strukt + .where_clause() + .and_then(|wc| rename_strukt_args(ctx, ast_strukt, &wc, &args)); + (field_ty, where_clause) + } + None => (field_ty.clone(), None), + }; - // 1) Resolve conflicts between generic parameters in old_impl and - // those in strukt. - // - // These generics parameters will also be used in `field_ty` and - // `where_clauses`, so we should substitute arguments in them as well. - let strukt_params = resolve_name_conflicts(strukt_params, &old_impl_params); - let (field_ty, ty_where_clause) = match &strukt_params { - Some(strukt_params) => { - let args = strukt_params.to_generic_args(&make); - let field_ty = rename_strukt_args(ctx, ast_strukt, field_ty, &args)?; - let where_clause = ast_strukt - .where_clause() - .and_then(|wc| rename_strukt_args(ctx, ast_strukt, &wc, &args)); - (field_ty, where_clause) - } - None => (field_ty.clone(), None), - }; - - // 2) Handle instantiated generics in `field_ty`. + // 2) Handle instantiated generics in `field_ty`. - // 2.1) Some generics used in `self_ty` may be instantiated, so they - // are no longer generics, we should remove and instantiate those - // generics in advance. + // 2.1) Some generics used in `self_ty` may be instantiated, so they + // are no longer generics, we should remove and instantiate those + // generics in advance. - // `old_trait_args` contains names of generic args for trait in `old_impl` - let old_impl_trait_args = old_impl - .trait_()? - .generic_arg_list() - .map(|l| l.generic_args().map(|arg| arg.to_string())) - .map_or_else(FxHashSet::default, |it| it.collect()); + // `old_trait_args` contains names of generic args for trait in `old_impl` + let old_impl_trait_args = old_trait + .generic_arg_list() + .map(|l| l.generic_args().map(|arg| arg.to_string())) + .map_or_else(FxHashSet::default, |it| it.collect()); - let trait_gen_params = remove_instantiated_params( - &old_impl.self_ty()?, - old_impl_params.clone(), - &old_impl_trait_args, - ); + let trait_gen_params = remove_instantiated_params( + &old_self_ty, + old_impl_params.clone(), + &old_impl_trait_args, + ); - // 2.2) Generate generic args applied on impl. - let (transform_args, trait_gen_params) = generate_args_for_impl( - &make, - old_impl_params, - &old_impl.self_ty()?, - &field_ty, - trait_gen_params, - &old_impl_trait_args, - ); + // 2.2) Generate generic args applied on impl. + let (transform_args, trait_gen_params) = generate_args_for_impl( + &make, + old_impl_params, + &old_self_ty, + &field_ty, + trait_gen_params, + &old_impl_trait_args, + ); - // 2.3) Instantiate generics with `transform_impl`, this step also - // remove unused params. - let trait_gen_args = - old_impl.trait_()?.generic_arg_list().and_then(|mut trait_args| { - let trait_args = &mut trait_args; - if let Some(new_args) = transform_impl( - ctx, - ast_strukt, - &old_impl, + // 2.3) Instantiate generics with `transform_impl`, this step also + // remove unused params. + let trait_gen_args = old_trait.generic_arg_list().map(|trait_args| { + transform_impl( + &source_scope, + &target_scope, + hir_old_impl, &transform_args, trait_args.clone(), - ) { - *trait_args = new_args.clone(); - Some(new_args) - } else { - None - } + ) }); - let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args(&make)); - let path_type = make.ty(&trait_.name(db).display_no_db(edition).to_smolstr()); - let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?; - // 3) Generate delegate trait impl - let delegate = make.impl_trait( - None, - trait_.is_unsafe(db), - trait_gen_params, - trait_gen_args, - strukt_params, - type_gen_args, - trait_.is_auto(db), - path_type, - strukt_ty, - old_impl.where_clause(), - ty_where_clause, - None, - ); - // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths - let qualified_path_type = - make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); - - // 4) Transform associated items in delegate trait impl - let assoc_items: Option> = old_impl.assoc_item_list().map(|ail| { - ail.assoc_items() - .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) - .filter_map(|item| { - let item = - transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?; - process_assoc_item(item, qualified_path_type.clone(), field_name) - }) - .collect() - }); - - finalize_delegate(&make, &delegate, assoc_items, true) + let type_gen_args = + strukt_params.clone().map(|params| params.to_generic_args(&make)); + let path_type = make.ty(&trait_.name(db).display_no_db(edition).to_smolstr()); + let path_type = transform_impl( + &source_scope, + &target_scope, + hir_old_impl, + &transform_args, + path_type, + ); + // 3) Generate delegate trait impl + let delegate = make.impl_trait( + None, + trait_.is_unsafe(db), + trait_gen_params, + trait_gen_args, + strukt_params, + type_gen_args, + trait_.is_auto(db), + path_type, + strukt_ty, + old_impl.where_clause(), + ty_where_clause, + None, + ); + // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths + let qualified_path_type = make.path_from_text(&format!( + "<{} as {}>", + field_ty, + delegate.trait_().unwrap() + )); + + // 4) Transform associated items in delegate trait impl + let assoc_items: Option> = + old_impl.assoc_item_list().map(|ail| { + ail.assoc_items() + .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) + .filter_map(|item| { + let item = transform_impl( + &source_scope, + &target_scope, + hir_old_impl, + &transform_args, + item, + ); + process_assoc_item(item, qualified_path_type.clone(), field_name) + }) + .collect() + }); + + finalize_delegate(&make, &delegate, assoc_items, true) + })) } } } fn transform_impl( - ctx: &AssistContext<'_, '_>, - strukt: &ast::Struct, - old_impl: &ast::Impl, + source_scope: &hir::SemanticsScope<'_>, + target_scope: &hir::SemanticsScope<'_>, + hir_old_impl: hir::Impl, args: &Option, syntax: N, -) -> Option { - let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?; - let target_scope = ctx.sema.scope(strukt.syntax())?; - let hir_old_impl = ctx.sema.to_impl_def(old_impl)?; - +) -> N { let transform = args.as_ref().map_or_else( - || PathTransform::generic_transformation(&target_scope, &source_scope), + || PathTransform::generic_transformation(target_scope, source_scope), |args| { PathTransform::impl_transformation( - &target_scope, - &source_scope, + target_scope, + source_scope, hir_old_impl, args.clone(), ) }, ); - N::cast(transform.apply(syntax.syntax())) + N::cast(transform.apply(syntax.syntax())).unwrap() } /// Extracts the name from a generic parameter. @@ -560,11 +594,11 @@ fn finalize_delegate( delegate: &ast::Impl, assoc_items: Option>, remove_where_clauses: bool, -) -> Option { +) -> ast::Impl { let has_items = assoc_items.as_ref().is_some_and(|items| !items.is_empty()); if !has_items && !remove_where_clauses { - return Some(delegate.clone()); + return delegate.clone(); } let (editor, delegate) = SyntaxEditor::with_ast_node(delegate); @@ -584,7 +618,7 @@ fn finalize_delegate( remove_useless_where_clauses(&editor, &delegate); } - ast::Impl::cast(editor.finish().new_root().clone()) + ast::Impl::cast(editor.finish().new_root().clone()).unwrap() } // Generate generic args that should be apply to current impl. @@ -1866,6 +1900,89 @@ impl T for B { ); } + #[test] + fn test_fn_inside_macros() { + check_assist( + generate_delegate_trait, + r#" +macro_rules! id { ($($t:tt)*) => {$($t)*}; } +id! { + struct A; + + trait T { + fn f(&mut self, a: u32); + } + + impl T for A { + fn f(&mut self, a: u32) {} + } +} + +struct B { + a$0: A, +} +"#, + r#" +macro_rules! id { ($($t:tt)*) => {$($t)*}; } +id! { + struct A; + + trait T { + fn f(&mut self, a: u32); + } + + impl T for A { + fn f(&mut self, a: u32) {} + } +} + +struct B { + a: A, +} + +impl T for B { + fn f(&mut self,a: u32) { + ::f(&mut self.a, a) + } +} +"#, + ); + + check_assist( + generate_delegate_trait, + r#" +macro_rules! id { ($($t:tt)*) => {$($t)*}; } +id! { + trait T { + fn f(&mut self, a: u32); + } +} + +struct B { + a$0: U, +} +"#, + r#" +macro_rules! id { ($($t:tt)*) => {$($t)*}; } +id! { + trait T { + fn f(&mut self, a: u32); + } +} + +struct B { + a: U, +} + +impl T for B { + fn f(&mut self,a: u32) { + ::f(&mut self.a, a) + } +} +"#, + ); + } + #[test] fn test_ty_alias_attrs() { check_assist( diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 8897e59355e4..406ae0fd90d0 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -1,11 +1,7 @@ use std::collections::BTreeSet; use either::Either; -use hir::{ - FileRange, PathResolution, Semantics, TypeInfo, - db::{ExpandDatabase, HirDatabase}, - sym, -}; +use hir::{FileRange, PathResolution, Semantics, TypeInfo, db::HirDatabase, sym}; use ide_db::{ EditionedFileId, FxHashMap, RootDatabase, base_db::Crate, @@ -13,7 +9,7 @@ use ide_db::{ imports::insert_use::remove_use_tree_if_simple, path_transform::PathTransform, search::{FileReference, FileReferenceNode, SearchScope}, - syntax_helpers::{node_ext::expr_as_name_ref, prettify_macro_expansion}, + syntax_helpers::node_ext::expr_as_name_ref, }; use itertools::{Itertools, izip}; use syntax::{ @@ -29,6 +25,7 @@ use syntax::{ use crate::{ AssistId, assist_context::{AssistContext, Assists}, + utils::pretty_node_inside_macro, }; // Assist: inline_into_callers @@ -350,15 +347,10 @@ fn inline( ) -> ast::Expr { let make = file_editor.make(); let file_id = sema.hir_file_for(fn_body.syntax()); - let body_to_clone = if let Some(macro_file) = file_id.macro_file() { + if file_id.is_macro() { cov_mark::hit!(inline_call_defined_in_macro); - let span_map = sema.db.expansion_span_map(macro_file); - let body_prettified = - prettify_macro_expansion(sema.db, fn_body.syntax().clone(), span_map, *krate); - if let Some(body) = ast::BlockExpr::cast(body_prettified) { body } else { fn_body.clone() } - } else { - fn_body.clone() - }; + } + let body_to_clone = pretty_node_inside_macro(fn_body.clone(), sema, file_id, *krate); // Capture before `with_ast_node` re-roots and loses the source-relative position. let mut original_body_indent = IndentLevel::from_node(body_to_clone.syntax()); @@ -493,21 +485,9 @@ fn inline( let expr: &ast::Expr = expr; let mut insert_let_stmt = || { - let param_ty = param_ty.clone().map(|param_ty| { - let file_id = sema.hir_file_for(param_ty.syntax()); - if let Some(macro_file) = file_id.macro_file() { - let span_map = sema.db.expansion_span_map(macro_file); - let param_ty_prettified = prettify_macro_expansion( - sema.db, - param_ty.syntax().clone(), - span_map, - *krate, - ); - ast::Type::cast(param_ty_prettified).unwrap_or(param_ty) - } else { - param_ty - } - }); + let param_ty = param_ty + .clone() + .map(|param_ty| pretty_node_inside_macro(param_ty, sema, file_id, *krate)); let ty = sema.type_of_expr(expr).filter(TypeInfo::has_adjustment).and(param_ty); diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index ff2d0544b2a1..050b1d97ee49 100644 --- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -1,4 +1,3 @@ -use hir::db::ExpandDatabase; use itertools::Itertools; use std::iter::successors; @@ -502,20 +501,11 @@ fn pretty_pat_inside_macro( pat: Option, sema: &hir::Semantics<'_, RootDatabase>, ) -> Option { - let pretty = |pat| { - let db = sema.db; - let scope = sema.scope(&pat)?; - let file_id = scope.file_id().macro_file()?; - // Don't call `prettify_macro_expansion()` outside the actual assist action; see inline_macro assist - let pretty_node = hir::prettify_macro_expansion( - db, - pat, - db.expansion_span_map(file_id), - scope.module().krate(db).into(), - ); - ast::Pat::cast(pretty_node) + let pretty = |pat: ast::Pat| { + let scope = sema.scope(pat.syntax())?; + Some(crate::utils::pretty_node_inside_macro(pat, sema, scope.file_id(), scope.krate())) }; - pat.map(|pat| pretty(pat.syntax().clone()).unwrap_or(pat)) + pat.map(|pat| pretty(pat.clone()).unwrap_or(pat)) } #[cfg(test)] diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 1b6c9a579aba..6b2bebe70dd6 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -219,24 +219,7 @@ pub fn add_trait_assoc_items_to_impl( original_items .iter() .map(|InFile { file_id, value: original_item }| { - let mut cloned_item = { - if let Some(macro_file) = file_id.macro_file() { - let span_map = sema.db.expansion_span_map(macro_file); - let item_prettified = prettify_macro_expansion( - sema.db, - original_item.syntax().clone(), - span_map, - target_scope.krate().into(), - ); - if let Some(formatted) = ast::AssocItem::cast(item_prettified) { - return formatted; - } else { - stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`"); - } - } - original_item - } - .reset_indent(); + let mut cloned_item = original_item.reset_indent(); if let Some(source_scope) = sema.scope(original_item.syntax()) { // FIXME: Paths in nested macros are not handled well. See @@ -245,6 +228,8 @@ pub fn add_trait_assoc_items_to_impl( PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap(); } + let cloned_item = + pretty_node_inside_macro(cloned_item, sema, *file_id, target_scope.krate()); let (editor, cloned_item) = SyntaxEditor::with_ast_node(&cloned_item); cloned_item.remove_attrs_and_docs(&editor); ast::AssocItem::cast(editor.finish().new_root().clone()).unwrap() @@ -252,10 +237,7 @@ pub fn add_trait_assoc_items_to_impl( .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { let (fn_editor, fn_) = SyntaxEditor::with_ast_node(&fn_); - let fill_expr: ast::Expr = match config.expr_fill_default { - ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), - ExprFillDefaultMode::Underscore => make.expr_underscore().into(), - }; + let fill_expr: ast::Expr = expr_fill_default(config); let new_body = make.block_expr(None::, Some(fill_expr)); fn_.replace_or_insert_body(&fn_editor, new_body); let new_fn_ = fn_editor.finish().new_root().clone(); @@ -275,6 +257,31 @@ pub fn add_trait_assoc_items_to_impl( .collect() } +pub(crate) fn pretty_node_inside_macro( + node: T, + sema: &Semantics<'_, RootDatabase>, + file_id: hir::HirFileId, + target_krate: impl Into, +) -> T { + match file_id.macro_file() { + Some(file_id) => { + // Don't call `prettify_macro_expansion()` outside the actual assist action; see inline_macro assist + let pretty_node = prettify_macro_expansion( + sema.db, + node.syntax().clone(), + sema.db.expansion_span_map(file_id), + target_krate.into(), + ); + let Some(new_node) = T::cast(pretty_node) else { + stdx::never!("prettify_macro_expansion changes node kind"); + return node; + }; + new_node + } + None => node, + } +} + pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { node.children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) diff --git a/crates/syntax-bridge/src/prettify_macro_expansion.rs b/crates/syntax-bridge/src/prettify_macro_expansion.rs index 001c920c9b82..bd8aa4b8ef73 100644 --- a/crates/syntax-bridge/src/prettify_macro_expansion.rs +++ b/crates/syntax-bridge/src/prettify_macro_expansion.rs @@ -4,7 +4,7 @@ use syntax::{ SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, T, WalkEvent, ast::syntax_factory::SyntaxFactory, - syntax_editor::{Position, SyntaxEditor}, + syntax_editor::{Element, Position, SyntaxEditor}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -31,8 +31,8 @@ pub fn prettify_macro_expansion( let mut dollar_crate_replacements = Vec::new(); let (editor, syn) = SyntaxEditor::new(syn); - let before = Position::before; - let after = Position::after; + let before = before_non_wrap; + let after = after_non_wrap; let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| { (pos(token.clone()), PrettifyWsKind::Indent(indent)) @@ -56,9 +56,9 @@ pub fn prettify_macro_expansion( _ => false, }; if (!is_last_child && is_non_last_newline) || is_always_newline { - mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent))); + mods.push((after_non_wrap(node.clone()), PrettifyWsKind::Indent(indent))); if node.parent().is_some() { - mods.push((Position::after(node), PrettifyWsKind::Newline)); + mods.push((after_non_wrap(node), PrettifyWsKind::Newline)); } } continue; @@ -176,6 +176,25 @@ pub fn prettify_macro_expansion( editor.finish().new_root().clone() } +fn before_non_wrap(elem: impl Element) -> Position { + let mut elem = elem.syntax_element(); + while elem.prev_sibling_or_token().is_none() + && let Some(parent) = elem.parent() + { + elem = parent.into(); + } + Position::before(elem) +} +fn after_non_wrap(elem: impl Element) -> Position { + let mut elem = elem.syntax_element(); + while elem.next_sibling_or_token().is_none() + && let Some(parent) = elem.parent() + { + elem = parent.into(); + } + Position::after(elem) +} + fn is_text(k: SyntaxKind) -> bool { // Consider all keywords in all editions. k.is_any_identifier() || k.is_literal() || k == UNDERSCORE @@ -187,17 +206,12 @@ mod tests { use expect_test::{Expect, expect}; #[expect(deprecated)] - fn check_pretty(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + fn pretty_input(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> SyntaxNode { 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); + return prettify_macro_expansion(syn, &mut |_, _| None, |_| ()); fn remove_whitespaces(node: &SyntaxNode) -> SyntaxNode { let (editor, node) = SyntaxEditor::new(node.clone()); @@ -211,6 +225,20 @@ mod tests { } } + fn check_pretty(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + let pretty = pretty_input(ra_fixture); + let mut pretty = pretty.to_string(); + if pretty.contains('\n') { + pretty.push('\n'); + } + expect.assert_eq(&pretty); + } + + fn check_pretty_ast(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + let pretty = pretty_input(ra_fixture); + expect.assert_eq(&format!("{pretty:#?}")); + } + #[test] fn test_in_macro() { check_pretty( @@ -470,4 +498,41 @@ mod tests { "#]], ); } + + #[test] + fn test_insert_outside_node() { + check_pretty_ast( + r#" + pub fn foo() -> i32 {} + "#, + expect![[r#" + SOURCE_FILE@0..22 + FN@0..22 + VISIBILITY@0..3 + PUB_KW@0..3 "pub" + WHITESPACE@3..4 " " + FN_KW@4..6 "fn" + WHITESPACE@6..7 " " + NAME@7..10 + IDENT@7..10 "foo" + PARAM_LIST@10..12 + L_PAREN@10..11 "(" + R_PAREN@11..12 ")" + WHITESPACE@12..13 " " + RET_TYPE@13..19 + THIN_ARROW@13..15 "->" + WHITESPACE@15..16 " " + PATH_TYPE@16..19 + PATH@16..19 + PATH_SEGMENT@16..19 + NAME_REF@16..19 + IDENT@16..19 "i32" + WHITESPACE@19..20 " " + BLOCK_EXPR@20..22 + STMT_LIST@20..22 + L_CURLY@20..21 "{" + R_CURLY@21..22 "}" + "#]], + ); + } }