Skip to content

Commit 5cbde9f

Browse files
bors[bot]Veykril
andauthored
Merge #8591 #8638
8591: Remove SyntaxRewriter usage in insert_use in favor of mutable syntax trees r=matklad a=Veykril Unfortunately changing `insert_use` to not use `SyntaxRewriter` creates a lot of changes since so much relies on that. But on the other hand this should be the biggest usage of `SyntaxRewriter` I believe. 8638: Remove SyntaxRewriter::from_fn r=Veykril a=Veykril Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
3 parents 20f8219 + d5c9de6 + e6e4417 commit 5cbde9f

File tree

15 files changed

+331
-355
lines changed

15 files changed

+331
-355
lines changed

crates/ide_assists/src/ast_transform.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
33
use ide_db::helpers::mod_path_to_ast;
44
use rustc_hash::FxHashMap;
55
use syntax::{
6-
algo::SyntaxRewriter,
76
ast::{self, AstNode},
8-
SyntaxNode,
7+
ted, SyntaxNode,
98
};
109

11-
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
12-
SyntaxRewriter::from_fn(|element| match element {
13-
syntax::SyntaxElement::Node(n) => {
14-
let replacement = transformer.get_substitution(&n, transformer)?;
15-
Some(replacement.into())
10+
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
11+
let mut skip_to = None;
12+
for event in node.syntax().preorder() {
13+
match event {
14+
syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
15+
skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
16+
}
17+
syntax::WalkEvent::Enter(_) => (),
18+
syntax::WalkEvent::Leave(node) => match &skip_to {
19+
Some((replacement, skip_target)) if *skip_target == node => {
20+
ted::replace(node, replacement.clone_for_update());
21+
skip_to.take();
22+
}
23+
_ => (),
24+
},
1625
}
17-
_ => None,
18-
})
19-
.rewrite_ast(&node)
26+
}
2027
}
2128

2229
/// `AstTransform` helps with applying bulk transformations to syntax nodes.
@@ -191,11 +198,9 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
191198
let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192199
let mut path = mod_path_to_ast(&found_path);
193200

194-
let type_args = p
195-
.segment()
196-
.and_then(|s| s.generic_arg_list())
197-
.map(|arg_list| apply(recur, arg_list));
201+
let type_args = p.segment().and_then(|s| s.generic_arg_list());
198202
if let Some(type_args) = type_args {
203+
apply(recur, &type_args);
199204
let last_segment = path.segment().unwrap();
200205
path = path.with_segment(last_segment.with_generic_args(type_args))
201206
}

crates/ide_assists/src/handlers/auto_import.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,19 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
9393

9494
let range = ctx.sema.original_range(&syntax_under_caret).range;
9595
let group_label = group_label(import_assets.import_candidate());
96-
let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
96+
let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?;
9797
for import in proposed_imports {
9898
acc.add_group(
9999
&group_label,
100100
AssistId("auto_import", AssistKind::QuickFix),
101101
format!("Import `{}`", import.import_path),
102102
range,
103103
|builder| {
104-
let rewriter =
105-
insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
106-
builder.rewrite(rewriter);
104+
let scope = match scope.clone() {
105+
ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
106+
ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
107+
};
108+
insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
107109
},
108110
);
109111
}

crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs

Lines changed: 110 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ use hir::{Module, ModuleDef, Name, Variant};
55
use ide_db::{
66
defs::Definition,
77
helpers::{
8-
insert_use::{insert_use, ImportScope},
8+
insert_use::{insert_use, ImportScope, InsertUseConfig},
99
mod_path_to_ast,
1010
},
1111
search::FileReference,
1212
RootDatabase,
1313
};
1414
use rustc_hash::FxHashSet;
1515
use syntax::{
16-
algo::{find_node_at_offset, SyntaxRewriter},
17-
ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
18-
SourceFile, SyntaxElement, SyntaxNode, T,
16+
algo::find_node_at_offset,
17+
ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18+
ted, SyntaxNode, T,
1919
};
2020

2121
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,40 +62,50 @@ pub(crate) fn extract_struct_from_enum_variant(
6262
let mut visited_modules_set = FxHashSet::default();
6363
let current_module = enum_hir.module(ctx.db());
6464
visited_modules_set.insert(current_module);
65-
let mut def_rewriter = None;
65+
// record file references of the file the def resides in, we only want to swap to the edited file in the builder once
66+
let mut def_file_references = None;
6667
for (file_id, references) in usages {
67-
let mut rewriter = SyntaxRewriter::default();
68-
let source_file = ctx.sema.parse(file_id);
69-
for reference in references {
70-
update_reference(
71-
ctx,
72-
&mut rewriter,
73-
reference,
74-
&source_file,
75-
&enum_module_def,
76-
&variant_hir_name,
77-
&mut visited_modules_set,
78-
);
79-
}
8068
if file_id == ctx.frange.file_id {
81-
def_rewriter = Some(rewriter);
69+
def_file_references = Some(references);
8270
continue;
8371
}
8472
builder.edit_file(file_id);
85-
builder.rewrite(rewriter);
73+
let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
74+
let processed = process_references(
75+
ctx,
76+
&mut visited_modules_set,
77+
source_file.syntax(),
78+
&enum_module_def,
79+
&variant_hir_name,
80+
references,
81+
);
82+
processed.into_iter().for_each(|(path, node, import)| {
83+
apply_references(ctx.config.insert_use, path, node, import)
84+
});
8685
}
87-
let mut rewriter = def_rewriter.unwrap_or_default();
88-
update_variant(&mut rewriter, &variant);
89-
extract_struct_def(
90-
&mut rewriter,
91-
&enum_ast,
92-
variant_name.clone(),
93-
&field_list,
94-
&variant.parent_enum().syntax().clone().into(),
95-
enum_ast.visibility(),
96-
);
9786
builder.edit_file(ctx.frange.file_id);
98-
builder.rewrite(rewriter);
87+
let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
88+
let variant = builder.make_ast_mut(variant.clone());
89+
if let Some(references) = def_file_references {
90+
let processed = process_references(
91+
ctx,
92+
&mut visited_modules_set,
93+
source_file.syntax(),
94+
&enum_module_def,
95+
&variant_hir_name,
96+
references,
97+
);
98+
processed.into_iter().for_each(|(path, node, import)| {
99+
apply_references(ctx.config.insert_use, path, node, import)
100+
});
101+
}
102+
103+
let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
104+
let start_offset = &variant.parent_enum().syntax().clone();
105+
ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106+
ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107+
108+
update_variant(&variant);
99109
},
100110
)
101111
}
@@ -136,34 +146,11 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
136146
.any(|(name, _)| name.to_string() == variant_name.to_string())
137147
}
138148

139-
fn insert_import(
140-
ctx: &AssistContext,
141-
rewriter: &mut SyntaxRewriter,
142-
scope_node: &SyntaxNode,
143-
module: &Module,
144-
enum_module_def: &ModuleDef,
145-
variant_hir_name: &Name,
146-
) -> Option<()> {
147-
let db = ctx.db();
148-
let mod_path =
149-
module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
150-
if let Some(mut mod_path) = mod_path {
151-
mod_path.pop_segment();
152-
mod_path.push_segment(variant_hir_name.clone());
153-
let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
154-
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
155-
}
156-
Some(())
157-
}
158-
159-
fn extract_struct_def(
160-
rewriter: &mut SyntaxRewriter,
161-
enum_: &ast::Enum,
149+
fn create_struct_def(
162150
variant_name: ast::Name,
163151
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
164-
start_offset: &SyntaxElement,
165152
visibility: Option<ast::Visibility>,
166-
) -> Option<()> {
153+
) -> ast::Struct {
167154
let pub_vis = Some(make::visibility_pub());
168155
let field_list = match field_list {
169156
Either::Left(field_list) => {
@@ -180,65 +167,90 @@ fn extract_struct_def(
180167
.into(),
181168
};
182169

183-
rewriter.insert_before(
184-
start_offset,
185-
make::struct_(visibility, variant_name, None, field_list).syntax(),
186-
);
187-
rewriter.insert_before(start_offset, &make::tokens::blank_line());
188-
189-
if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
190-
rewriter
191-
.insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
192-
}
193-
Some(())
170+
make::struct_(visibility, variant_name, None, field_list).clone_for_update()
194171
}
195172

196-
fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
173+
fn update_variant(variant: &ast::Variant) -> Option<()> {
197174
let name = variant.name()?;
198175
let tuple_field = make::tuple_field(None, make::ty(&name.text()));
199176
let replacement = make::variant(
200177
name,
201178
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
202-
);
203-
rewriter.replace(variant.syntax(), replacement.syntax());
179+
)
180+
.clone_for_update();
181+
ted::replace(variant.syntax(), replacement.syntax());
204182
Some(())
205183
}
206184

207-
fn update_reference(
185+
fn apply_references(
186+
insert_use_cfg: InsertUseConfig,
187+
segment: ast::PathSegment,
188+
node: SyntaxNode,
189+
import: Option<(ImportScope, hir::ModPath)>,
190+
) {
191+
if let Some((scope, path)) = import {
192+
insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
193+
}
194+
ted::insert_raw(
195+
ted::Position::before(segment.syntax()),
196+
make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
197+
);
198+
ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
199+
ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
200+
}
201+
202+
fn process_references(
208203
ctx: &AssistContext,
209-
rewriter: &mut SyntaxRewriter,
210-
reference: FileReference,
211-
source_file: &SourceFile,
204+
visited_modules: &mut FxHashSet<Module>,
205+
source_file: &SyntaxNode,
212206
enum_module_def: &ModuleDef,
213207
variant_hir_name: &Name,
214-
visited_modules_set: &mut FxHashSet<Module>,
215-
) -> Option<()> {
208+
refs: Vec<FileReference>,
209+
) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
210+
// we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
211+
// and corresponding nodes up front
212+
refs.into_iter()
213+
.flat_map(|reference| {
214+
let (segment, scope_node, module) =
215+
reference_to_node(&ctx.sema, source_file, reference)?;
216+
if !visited_modules.contains(&module) {
217+
let mod_path = module.find_use_path_prefixed(
218+
ctx.sema.db,
219+
*enum_module_def,
220+
ctx.config.insert_use.prefix_kind,
221+
);
222+
if let Some(mut mod_path) = mod_path {
223+
mod_path.pop_segment();
224+
mod_path.push_segment(variant_hir_name.clone());
225+
let scope = ImportScope::find_insert_use_container(&scope_node)?;
226+
visited_modules.insert(module);
227+
return Some((segment, scope_node, Some((scope, mod_path))));
228+
}
229+
}
230+
Some((segment, scope_node, None))
231+
})
232+
.collect()
233+
}
234+
235+
fn reference_to_node(
236+
sema: &hir::Semantics<RootDatabase>,
237+
source_file: &SyntaxNode,
238+
reference: FileReference,
239+
) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
216240
let offset = reference.range.start();
217-
let (segment, expr) = if let Some(path_expr) =
218-
find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
219-
{
241+
if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
220242
// tuple variant
221-
(path_expr.path()?.segment()?, path_expr.syntax().parent()?)
222-
} else if let Some(record_expr) =
223-
find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
224-
{
243+
Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
244+
} else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
225245
// record variant
226-
(record_expr.path()?.segment()?, record_expr.syntax().clone())
246+
Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
227247
} else {
228-
return None;
229-
};
230-
231-
let module = ctx.sema.scope(&expr).module()?;
232-
if !visited_modules_set.contains(&module) {
233-
if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
234-
{
235-
visited_modules_set.insert(module);
236-
}
248+
None
237249
}
238-
rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
239-
rewriter.insert_after(segment.syntax(), segment.syntax());
240-
rewriter.insert_after(&expr, &make::token(T![')']));
241-
Some(())
250+
.and_then(|(segment, expr)| {
251+
let module = sema.scope(&expr).module()?;
252+
Some((segment, expr, module))
253+
})
242254
}
243255

244256
#[cfg(test)]
@@ -345,7 +357,7 @@ mod my_mod {
345357
346358
pub struct MyField(pub u8, pub u8);
347359
348-
pub enum MyEnum {
360+
pub enum MyEnum {
349361
MyField(MyField),
350362
}
351363
}

crates/ide_assists/src/handlers/reorder_fields.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,9 @@ fn replace<T: AstNode + PartialEq>(
8383
fields: impl Iterator<Item = T>,
8484
sorted_fields: impl IntoIterator<Item = T>,
8585
) {
86-
fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each(
87-
|(field, sorted_field)| {
88-
ted::replace(field.syntax(), sorted_field.syntax().clone_for_update());
89-
},
90-
);
86+
fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
87+
ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
88+
});
9189
}
9290

9391
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {

0 commit comments

Comments
 (0)