Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 79 additions & 39 deletions crates/ide-assists/src/handlers/extract_module.rs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please migrate remaining make::* usages to SyntaxFactory APIs

Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use syntax::{
self, HasVisibility,
edit::{AstNodeEdit, IndentLevel},
make,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly replace use of make with SyntaxFactory

syntax_factory::SyntaxFactory,
},
match_ast, ted,
match_ast,
syntax_editor::{Position, SyntaxEditor},
};

use crate::{AssistContext, Assists};
Expand Down Expand Up @@ -186,6 +188,7 @@ fn generate_module_def(
parent_impl: &Option<ast::Impl>,
Module { name, body_items, use_items }: &Module,
) -> ast::Module {
let make = SyntaxFactory::without_mappings();
let items: Vec<_> = if let Some(impl_) = parent_impl.as_ref()
&& let Some(self_ty) = impl_.self_ty()
{
Expand All @@ -195,9 +198,16 @@ fn generate_module_def(
.filter_map(ast::AssocItem::cast)
.map(|it| it.indent(IndentLevel(1)))
.collect_vec();
let assoc_item_list = make::assoc_item_list(Some(assoc_items)).clone_for_update();
let impl_ = impl_.reset_indent();
ted::replace(impl_.get_or_create_assoc_item_list().syntax(), assoc_item_list.syntax());
let impl_detached = ast::Impl::cast(impl_.syntax().clone_subtree()).unwrap();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove unwraps

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

let (mut editor, _) = SyntaxEditor::new(impl_detached.syntax().clone());
let make = SyntaxFactory::with_mappings();
let assoc_item_list = make.assoc_item_list(assoc_items);
if let Some(existing_list) = impl_detached.assoc_item_list() {
editor.replace(existing_list.syntax(), assoc_item_list.syntax());
}
editor.add_mappings(make.finish_with_mappings());
let new_impl_node = editor.finish().new_root().clone();
let impl_ = ast::Impl::cast(new_impl_node).unwrap().reset_indent();
// Add the import for enum/struct corresponding to given impl block
let use_impl = make_use_stmt_of_node_with_super(self_ty.syntax());
once(use_impl)
Expand All @@ -209,10 +219,10 @@ fn generate_module_def(
};

let items = items.into_iter().map(|it| it.reset_indent().indent(IndentLevel(1))).collect_vec();
let module_body = make::item_list(Some(items));
let module_body = make.item_list(items);

let module_name = make::name(name);
make::mod_(module_name, Some(module_body))
let module_name = make.name(name);
make.mod_(module_name, Some(module_body))
}

fn make_use_stmt_of_node_with_super(node_syntax: &SyntaxNode) -> ast::Item {
Expand Down Expand Up @@ -385,18 +395,28 @@ impl Module {
if use_.syntax().parent().is_some_and(|parent| parent == covering_node)
&& use_stmts_set.insert(use_.syntax().text_range().start())
{
let use_ = use_stmts_to_be_inserted
let entry = use_stmts_to_be_inserted
.entry(use_.syntax().text_range().start())
.or_insert_with(|| use_.clone_subtree().clone_for_update());
for seg in use_
.or_insert_with(|| use_.clone_subtree());
Copy link
Copy Markdown
Member

@Shourya742 Shourya742 Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View changes since the review

remove clone_subtree

let make = SyntaxFactory::without_mappings();
let replacements: Vec<_> = entry
.syntax()
.descendants()
.filter_map(ast::NameRef::cast)
.filter(|seg| seg.syntax().to_string() == name_ref.to_string())
{
let new_ref = make::path_from_text(&format!("{mod_name}::{seg}"))
.clone_for_update();
ted::replace(seg.syntax().parent()?, new_ref.syntax());
.filter_map(|seg| {
Some((
seg.syntax().parent()?,
make.path_from_text(&format!("{mod_name}::{seg}")),
))
})
.collect();
if !replacements.is_empty() {
let (mut editor, _) = SyntaxEditor::new(entry.syntax().clone());
for (parent, new_ref) in &replacements {
editor.replace(parent, new_ref.syntax());
}
*entry = ast::Use::cast(editor.finish().new_root().clone()).unwrap();
}
}
}
Expand All @@ -408,8 +428,16 @@ impl Module {
}

fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
// Detach each item from the file tree while preserving correct indentation.
// reset_indent() must be called before detaching so IndentLevel::from_node
// can read the preceding whitespace; clone_subtree() then produces the
// immutable detached root that SyntaxEditor requires.
for item in &mut self.body_items {
*item = ast::Item::cast(item.reset_indent().syntax().clone_subtree()).unwrap();
}
Comment on lines +435 to +437
Copy link
Copy Markdown
Member

@Shourya742 Shourya742 Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View changes since the review

No need to call clone_subtree here anymore. SyntaxEditor manages this internally

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tried removing the clone_subtree() calls but found that 3 of the 4 calls require using the detached root returned by SyntaxEditor::new() instead of just dropping clone_subtree(). i think the 4th at line 436 has to stay because replacements is collected from body_items immediately after. is there something i'm missing or is there a different approach you had in mind?


let (mut replacements, record_field_parents, impls) =
get_replacements_for_visibility_change(&mut self.body_items, false);
get_replacements_for_visibility_change(&mut self.body_items);

let mut impl_items = impls
.into_iter()
Expand All @@ -418,7 +446,7 @@ impl Module {
.collect_vec();

let (mut impl_item_replacements, _, _) =
get_replacements_for_visibility_change(&mut impl_items, true);
get_replacements_for_visibility_change(&mut impl_items);

replacements.append(&mut impl_item_replacements);

Expand All @@ -432,17 +460,42 @@ impl Module {
}
}

for (vis, syntax) in replacements {
let item = syntax.children_with_tokens().find(|node_or_token| {
match node_or_token.kind() {
// We're skipping comments, doc comments, and attribute macros that may precede the keyword
// that the visibility should be placed before.
SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
_ => true,
}
});
let make = SyntaxFactory::without_mappings();
for body_item in &mut self.body_items {
let insert_targets: Vec<_> = replacements
.iter()
.filter(|(vis, syntax)| {
vis.is_none()
&& (syntax == body_item.syntax()
|| syntax.ancestors().any(|a| &a == body_item.syntax()))
})
.filter_map(|(_, syntax)| {
syntax.children_with_tokens().find(|nt| {
// We're skipping comments, doc comments, and attribute macros that may
// precede the keyword that the visibility should be placed before.
!matches!(
nt.kind(),
SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE
)
})
})
.collect();

add_change_vis(vis, item);
if insert_targets.is_empty() {
continue;
}

let (mut editor, _) = SyntaxEditor::new(body_item.syntax().clone());
for target in insert_targets {
editor.insert_all(
Position::before(target),
vec![
make.visibility_pub_crate().syntax().clone().into(),
make.whitespace(" ").into(),
],
);
}
*body_item = ast::Item::cast(editor.finish().new_root().clone_subtree()).unwrap();
Copy link
Copy Markdown
Member

@Shourya742 Shourya742 Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View changes since the review

Suggested change
*body_item = ast::Item::cast(editor.finish().new_root().clone_subtree()).unwrap();
*body_item = ast::Item::cast(editor.finish().new_root()).unwrap();

}
}

Expand Down Expand Up @@ -741,7 +794,6 @@ fn check_def_in_mod_and_out_sel(

fn get_replacements_for_visibility_change(
items: &mut [ast::Item],
is_clone_for_updated: bool,
) -> (
Vec<(Option<ast::Visibility>, SyntaxNode)>,
Vec<(Option<ast::Visibility>, SyntaxNode)>,
Expand All @@ -752,9 +804,6 @@ fn get_replacements_for_visibility_change(
let mut impls = Vec::new();

for item in items {
if !is_clone_for_updated {
*item = item.clone_for_update();
}
//Use stmts are ignored
macro_rules! push_to_replacement {
($it:ident) => {
Expand Down Expand Up @@ -811,15 +860,6 @@ fn get_use_tree_paths_from_path(
Some(use_tree_str)
}

fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
if vis.is_none()
&& let Some(node_or_token) = node_or_token_opt
{
let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
}
}

fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
node.siblings_with_tokens(syntax::Direction::Prev)
.find(|x| x.kind() == WHITESPACE)
Expand Down
33 changes: 31 additions & 2 deletions crates/syntax/src/ast/syntax_factory/constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use either::Either;
use crate::{
AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken,
ast::{
self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName,
HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasModuleItem,
HasName, HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
},
syntax_editor::SyntaxMappingBuilder,
};
Expand Down Expand Up @@ -1818,6 +1818,35 @@ impl SyntaxFactory {
make::assoc_item_list(None).clone_for_update()
}

pub fn item_list(&self, items: impl IntoIterator<Item = ast::Item>) -> ast::ItemList {
let (items, input) = iterator_input(items);
let items_vec: Vec<_> = items.into_iter().collect();
let ast = make::item_list(Some(items_vec)).clone_for_update();

if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_children(input, ast.items().map(|item: ast::Item| item.syntax().clone()));
builder.finish(&mut mapping);
}

ast
}

pub fn mod_(&self, name: ast::Name, body: Option<ast::ItemList>) -> ast::Module {
let ast = make::mod_(name.clone(), body.clone()).clone_for_update();

if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
if let Some(body) = body {
builder.map_node(body.syntax().clone(), ast.item_list().unwrap().syntax().clone());
}
builder.finish(&mut mapping);
}

ast
}

pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr {
let ast = make::attr_outer(meta.clone()).clone_for_update();

Expand Down