Skip to content

Commit 06aba0c

Browse files
Merge pull request #22261 from Shourya742/2026-05-03-migrate-missing-fields-to-syntax-editor
Migrate missing fields to syntax editor
2 parents ddc1508 + f80347b commit 06aba0c

4 files changed

Lines changed: 151 additions & 197 deletions

File tree

crates/ide-diagnostics/src/handlers/missing_fields.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use stdx::format_to;
1818
use syntax::{
1919
AstNode, Edition, SyntaxNode, SyntaxNodePtr, ToSmolStr,
2020
ast::{self, make},
21+
syntax_editor::SyntaxEditor,
2122
};
2223

2324
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
@@ -115,16 +116,20 @@ fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::MissingFields) -> Option<Vec
115116
}
116117
});
117118

119+
let old_field_list = field_list_parent.record_expr_field_list()?;
120+
let root = old_field_list.syntax().ancestors().last()?;
121+
let (editor, _) = SyntaxEditor::new(root);
122+
let make = editor.make();
123+
118124
let generate_fill_expr = |ty: &Type<'_>| match ctx.config.expr_fill_default {
119-
ExprFillDefaultMode::Todo => make::ext::expr_todo(),
120-
ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
125+
ExprFillDefaultMode::Todo => make.expr_todo(),
126+
ExprFillDefaultMode::Underscore => make.expr_underscore().into(),
121127
ExprFillDefaultMode::Default => {
122-
get_default_constructor(ctx, d, ty).unwrap_or_else(make::ext::expr_todo)
128+
get_default_constructor(ctx, d, ty).unwrap_or_else(|| make.expr_todo())
123129
}
124130
};
125131

126-
let old_field_list = field_list_parent.record_expr_field_list()?;
127-
let new_field_list = old_field_list.clone_for_update();
132+
let mut new_fields = Vec::new();
128133
for (f, ty) in missing_fields.iter() {
129134
let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {
130135
cov_mark::hit!(field_shorthand);
@@ -159,31 +164,39 @@ fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::MissingFields) -> Option<Vec
159164

160165
if expr.is_some() { expr } else { Some(generate_fill_expr(ty)) }
161166
};
162-
let field = make::record_expr_field(
163-
make::name_ref(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),
167+
let field = make.record_expr_field(
168+
make.name_ref(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),
164169
field_expr,
165170
);
166-
new_field_list.add_field(field.clone_for_update());
171+
new_fields.push(field);
167172
}
168-
build_text_edit(new_field_list.syntax(), old_field_list.syntax())
173+
old_field_list.add_fields(&editor, new_fields);
174+
let new_field_list = editor.finish().find_element(old_field_list.syntax())?;
175+
build_text_edit(&new_field_list, old_field_list.syntax())
169176
}
170177
Either::Right(field_list_parent) => {
171178
let missing_fields = ctx.sema.record_pattern_missing_fields(field_list_parent);
172179

173180
let old_field_list = field_list_parent.record_pat_field_list()?;
174-
let new_field_list = old_field_list.clone_for_update();
181+
let root = old_field_list.syntax().ancestors().last()?;
182+
let (editor, _) = SyntaxEditor::new(root);
183+
let make = editor.make();
184+
185+
let mut new_fields = Vec::new();
175186
for (f, _) in missing_fields.iter() {
176-
let field = make::record_pat_field_shorthand(
177-
make::ident_pat(
187+
let field = make.record_pat_field_shorthand(
188+
make.ident_pat(
178189
false,
179190
false,
180-
make::name(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),
191+
make.name(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),
181192
)
182193
.into(),
183194
);
184-
new_field_list.add_field(field.clone_for_update());
195+
new_fields.push(field);
185196
}
186-
build_text_edit(new_field_list.syntax(), old_field_list.syntax())
197+
old_field_list.add_fields(&editor, new_fields);
198+
let new_field_list = editor.finish().find_element(old_field_list.syntax())?;
199+
build_text_edit(&new_field_list, old_field_list.syntax())
187200
}
188201
}
189202
}

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 4 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
33
use std::iter::{empty, once, successors};
44

5-
use parser::{SyntaxKind, T};
5+
use parser::T;
66

77
use crate::{
8-
AstNode, AstToken, Direction, SyntaxElement,
8+
AstNode, AstToken, Direction,
99
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
10-
SyntaxNode, SyntaxToken,
10+
SyntaxNode,
1111
algo::{self, neighbor},
1212
ast::{self, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
13-
syntax_editor::{self, SyntaxEditor},
13+
syntax_editor::SyntaxEditor,
1414
ted,
1515
};
1616

@@ -270,75 +270,6 @@ impl ast::Impl {
270270
}
271271
}
272272

273-
impl ast::AssocItemList {
274-
/// Adds a new associated item after all of the existing associated items.
275-
///
276-
/// Attention! This function does align the first line of `item` with respect to `self`,
277-
/// but it does _not_ change indentation of other lines (if any).
278-
pub fn add_item(&self, editor: &SyntaxEditor, item: ast::AssocItem) {
279-
let make = editor.make();
280-
let (indent, position, whitespace) = match self.assoc_items().last() {
281-
Some(last_item) => (
282-
IndentLevel::from_node(last_item.syntax()),
283-
syntax_editor::Position::after(last_item.syntax()),
284-
"\n\n",
285-
),
286-
None => match self.l_curly_token() {
287-
Some(l_curly) => {
288-
normalize_ws_between_braces_with_editor(editor, self.syntax());
289-
(
290-
IndentLevel::from_token(&l_curly) + 1,
291-
syntax_editor::Position::after(&l_curly),
292-
"\n",
293-
)
294-
}
295-
None => (
296-
IndentLevel::zero(),
297-
syntax_editor::Position::last_child_of(self.syntax()),
298-
"\n",
299-
),
300-
},
301-
};
302-
let elements: Vec<SyntaxElement> = vec![
303-
make.whitespace(&format!("{whitespace}{indent}")).into(),
304-
item.syntax().clone().into(),
305-
];
306-
editor.insert_all(position, elements);
307-
}
308-
}
309-
310-
impl ast::RecordExprFieldList {
311-
pub fn add_field(&self, field: ast::RecordExprField) {
312-
let is_multiline = self.syntax().text().contains_char('\n');
313-
let whitespace = if is_multiline {
314-
let indent = IndentLevel::from_node(self.syntax()) + 1;
315-
make::tokens::whitespace(&format!("\n{indent}"))
316-
} else {
317-
make::tokens::single_space()
318-
};
319-
320-
if is_multiline {
321-
normalize_ws_between_braces(self.syntax());
322-
}
323-
324-
let position = match self.fields().last() {
325-
Some(last_field) => {
326-
let comma = get_or_insert_comma_after(last_field.syntax());
327-
ted::Position::after(comma)
328-
}
329-
None => match self.l_curly_token() {
330-
Some(it) => ted::Position::after(it),
331-
None => ted::Position::last_child_of(self.syntax()),
332-
},
333-
};
334-
335-
ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
336-
if is_multiline {
337-
ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,]));
338-
}
339-
}
340-
}
341-
342273
impl ast::RecordExprField {
343274
/// This will either replace the initializer, or in the case that this is a shorthand convert
344275
/// the initializer into the name ref and insert the expr as the new initializer.
@@ -360,110 +291,6 @@ impl ast::RecordExprField {
360291
}
361292
}
362293

363-
impl ast::RecordPatFieldList {
364-
pub fn add_field(&self, field: ast::RecordPatField) {
365-
let is_multiline = self.syntax().text().contains_char('\n');
366-
let whitespace = if is_multiline {
367-
let indent = IndentLevel::from_node(self.syntax()) + 1;
368-
make::tokens::whitespace(&format!("\n{indent}"))
369-
} else {
370-
make::tokens::single_space()
371-
};
372-
373-
if is_multiline {
374-
normalize_ws_between_braces(self.syntax());
375-
}
376-
377-
let position = match self.fields().last() {
378-
Some(last_field) => {
379-
let syntax = last_field.syntax();
380-
let comma = get_or_insert_comma_after(syntax);
381-
ted::Position::after(comma)
382-
}
383-
None => match self.l_curly_token() {
384-
Some(it) => ted::Position::after(it),
385-
None => ted::Position::last_child_of(self.syntax()),
386-
},
387-
};
388-
389-
ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
390-
if is_multiline {
391-
ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,]));
392-
}
393-
}
394-
}
395-
396-
fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken {
397-
match syntax
398-
.siblings_with_tokens(Direction::Next)
399-
.filter_map(|it| it.into_token())
400-
.find(|it| it.kind() == T![,])
401-
{
402-
Some(it) => it,
403-
None => {
404-
let comma = ast::make::token(T![,]);
405-
ted::insert(ted::Position::after(syntax), &comma);
406-
comma
407-
}
408-
}
409-
}
410-
411-
fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
412-
let l = node
413-
.children_with_tokens()
414-
.filter_map(|it| it.into_token())
415-
.find(|it| it.kind() == T!['{'])?;
416-
let r = node
417-
.children_with_tokens()
418-
.filter_map(|it| it.into_token())
419-
.find(|it| it.kind() == T!['}'])?;
420-
421-
let indent = IndentLevel::from_node(node);
422-
423-
match l.next_sibling_or_token() {
424-
Some(ws)
425-
if ws.kind() == SyntaxKind::WHITESPACE
426-
&& ws.next_sibling_or_token()?.into_token()? == r =>
427-
{
428-
ted::replace(ws, make::tokens::whitespace(&format!("\n{indent}")));
429-
}
430-
Some(ws) if ws.kind() == T!['}'] => {
431-
ted::insert(ted::Position::after(l), make::tokens::whitespace(&format!("\n{indent}")));
432-
}
433-
_ => (),
434-
}
435-
Some(())
436-
}
437-
438-
fn normalize_ws_between_braces_with_editor(editor: &SyntaxEditor, node: &SyntaxNode) -> Option<()> {
439-
let make = editor.make();
440-
let l = node
441-
.children_with_tokens()
442-
.filter_map(|it| it.into_token())
443-
.find(|it| it.kind() == T!['{'])?;
444-
let r = node
445-
.children_with_tokens()
446-
.filter_map(|it| it.into_token())
447-
.find(|it| it.kind() == T!['}'])?;
448-
449-
let indent = IndentLevel::from_node(node);
450-
451-
match l.next_sibling_or_token() {
452-
Some(ws)
453-
if ws.kind() == SyntaxKind::WHITESPACE
454-
&& ws.next_sibling_or_token()?.into_token()? == r =>
455-
{
456-
editor.replace(ws, make.whitespace(&format!("\n{indent}")));
457-
}
458-
Some(ws) if ws.kind() == T!['}'] => {
459-
editor
460-
.insert(syntax_editor::Position::after(l), make.whitespace(&format!("\n{indent}")));
461-
}
462-
_ => (),
463-
}
464-
Some(())
465-
}
466-
467294
pub trait Indent: AstNode + Clone + Sized {
468295
fn indent_level(&self) -> IndentLevel {
469296
IndentLevel::from_node(self.syntax())

crates/syntax/src/syntax_editor.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ impl SyntaxEdit {
216216
pub fn find_annotation(&self, annotation: SyntaxAnnotation) -> &[SyntaxElement] {
217217
self.annotations.get(&annotation).as_ref().map_or(&[], |it| it.as_slice())
218218
}
219+
220+
pub fn find_element(&self, old_node: &SyntaxNode) -> Option<SyntaxNode> {
221+
let old_root_start = self.old_root.text_range().start();
222+
let old_start = old_node.text_range().start() - old_root_start;
223+
let new_root_start = self.new_root.text_range().start();
224+
let kind = old_node.kind();
225+
226+
self.new_root
227+
.descendants()
228+
.find(|it| it.kind() == kind && it.text_range().start() - new_root_start == old_start)
229+
}
219230
}
220231

221232
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]

0 commit comments

Comments
 (0)