Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate introduce_named_generic Assist to Use SyntaxFactory #18483

Merged
Merged
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
49 changes: 23 additions & 26 deletions crates/ide-assists/src/handlers/introduce_named_generic.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use ide_db::syntax_helpers::suggest_name;
use itertools::Itertools;
use syntax::{
ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName},
ted,
};
use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName};

use crate::{AssistContext, AssistId, AssistKind, Assists};

Expand All @@ -25,42 +22,42 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>

let type_bound_list = impl_trait_type.type_bound_list()?;

let make = SyntaxFactory::new();
let target = fn_.syntax().text_range();
acc.add(
AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
"Replace impl trait with generic",
target,
|edit| {
let impl_trait_type = edit.make_mut(impl_trait_type);
let fn_ = edit.make_mut(fn_);
let fn_generic_param_list = fn_.get_or_create_generic_param_list();

let existing_names = fn_generic_param_list
.generic_params()
.flat_map(|param| match param {
ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
p => Some(p.to_string()),
})
.collect_vec();
|builder| {
let mut editor = builder.make_editor(fn_.syntax());

let existing_names = match fn_.generic_param_list() {
Some(generic_param_list) => generic_param_list
.generic_params()
.flat_map(|param| match param {
ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
p => Some(p.to_string()),
})
.collect_vec(),
None => Vec::new(),
};
let type_param_name = suggest_name::NameGenerator::new_with_names(
existing_names.iter().map(|s| s.as_str()),
)
.for_impl_trait_as_generic(&impl_trait_type);

let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
.clone_for_update();
let new_ty = make::ty(&type_param_name).clone_for_update();
let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list));
let new_ty = make.ty(&type_param_name);

ted::replace(impl_trait_type.syntax(), new_ty.syntax());
fn_generic_param_list.add_generic_param(type_param.into());
editor.replace(impl_trait_type.syntax(), new_ty.syntax());
editor.add_generic_param(&fn_, type_param.clone().into());

if let Some(cap) = ctx.config.snippet_cap {
if let Some(generic_param) =
fn_.generic_param_list().and_then(|it| it.generic_params().last())
{
edit.add_tabstop_before(cap, generic_param);
}
editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap));
}

editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.file_id(), editor);
},
)
}
Expand Down
28 changes: 27 additions & 1 deletion crates/syntax/src/ast/syntax_factory/constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use itertools::Itertools;

use crate::{
ast::{self, make, HasName},
ast::{self, make, HasName, HasTypeBounds},
syntax_editor::SyntaxMappingBuilder,
AstNode,
};
Expand All @@ -14,6 +14,32 @@ impl SyntaxFactory {
make::name(name).clone_for_update()
}

pub fn ty(&self, text: &str) -> ast::Type {
make::ty(text).clone_for_update()
}

pub fn type_param(
&self,
name: ast::Name,
bounds: Option<ast::TypeBoundList>,
) -> ast::TypeParam {
let ast = make::type_param(name.clone(), bounds.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(input) = bounds {
builder.map_node(
input.syntax().clone(),
ast.type_bound_list().unwrap().syntax().clone(),
);
}
builder.finish(&mut mapping);
}

ast
}

pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();

Expand Down
1 change: 1 addition & 0 deletions crates/syntax/src/syntax_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rustc_hash::FxHashMap;
use crate::{SyntaxElement, SyntaxNode, SyntaxToken};

mod edit_algo;
mod edits;
mod mapping;

pub use mapping::{SyntaxMapping, SyntaxMappingBuilder};
Expand Down
72 changes: 72 additions & 0 deletions crates/syntax/src/syntax_editor/edits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Structural editing for ast using `SyntaxEditor`

use crate::{
ast::make, ast::AstNode, ast::Fn, ast::GenericParam, ast::HasGenericParams, ast::HasName,
syntax_editor::Position, syntax_editor::SyntaxEditor, SyntaxKind,
};

impl SyntaxEditor {
/// Adds a new generic param to the function using `SyntaxEditor`
pub fn add_generic_param(&mut self, function: &Fn, new_param: GenericParam) {
match function.generic_param_list() {
Some(generic_param_list) => match generic_param_list.generic_params().last() {
Some(last_param) => {
// There exists a generic param list and it's not empty
let position = generic_param_list.r_angle_token().map_or_else(
|| Position::last_child_of(function.syntax()),
Position::before,
);

if last_param
.syntax()
.next_sibling_or_token()
.map_or(false, |it| it.kind() == SyntaxKind::COMMA)
{
self.insert(
Position::after(last_param.syntax()),
new_param.syntax().clone(),
);
self.insert(
Position::after(last_param.syntax()),
make::token(SyntaxKind::WHITESPACE),
);
self.insert(
Position::after(last_param.syntax()),
make::token(SyntaxKind::COMMA),
);
} else {
let elements = vec![
make::token(SyntaxKind::COMMA).into(),
make::token(SyntaxKind::WHITESPACE).into(),
new_param.syntax().clone().into(),
];
self.insert_all(position, elements);
}
}
None => {
// There exists a generic param list but it's empty
let position = Position::after(generic_param_list.l_angle_token().unwrap());
self.insert(position, new_param.syntax());
}
},
None => {
// There was no generic param list
let position = if let Some(name) = function.name() {
Position::after(name.syntax)
} else if let Some(fn_token) = function.fn_token() {
Position::after(fn_token)
} else if let Some(param_list) = function.param_list() {
Position::before(param_list.syntax)
} else {
Position::last_child_of(function.syntax())
};
let elements = vec![
make::token(SyntaxKind::L_ANGLE).into(),
new_param.syntax().clone().into(),
make::token(SyntaxKind::R_ANGLE).into(),
];
self.insert_all(position, elements);
}
}
}
}
Loading