Skip to content

Commit 002fcea

Browse files
authored
Merge pull request #18657 from Giga-Bowser/generate-enum-variant
minor: Migrate `generate_enum_variant` to `SyntaxEditor`
2 parents b0c82c9 + 547f75a commit 002fcea

File tree

6 files changed

+522
-231
lines changed

6 files changed

+522
-231
lines changed

crates/ide-assists/src/handlers/bool_to_enum.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -512,9 +512,11 @@ fn make_bool_enum(make_pub: bool) -> ast::Enum {
512512
let enum_def = make::enum_(
513513
if make_pub { Some(make::visibility_pub()) } else { None },
514514
make::name("Bool"),
515+
None,
516+
None,
515517
make::variant_list(vec![
516-
make::variant(make::name("True"), None),
517-
make::variant(make::name("False"), None),
518+
make::variant(None, make::name("True"), None, None),
519+
make::variant(None, make::name("False"), None, None),
518520
]),
519521
)
520522
.clone_for_update();

crates/ide-assists/src/handlers/generate_enum_variant.rs

+71-99
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use hir::{HasSource, HirDisplay, InRealFile};
22
use ide_db::assists::{AssistId, AssistKind};
33
use syntax::{
4-
ast::{self, make, HasArgList},
4+
ast::{self, syntax_factory::SyntaxFactory, HasArgList},
55
match_ast, AstNode, SyntaxNode,
66
};
77

@@ -33,7 +33,7 @@ use crate::assist_context::{AssistContext, Assists};
3333
// ```
3434
pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
3535
let path: ast::Path = ctx.find_node_at_offset()?;
36-
let parent = path_parent(&path)?;
36+
let parent = PathParent::new(&path)?;
3737

3838
if ctx.sema.resolve_path(&path).is_some() {
3939
// No need to generate anything if the path resolves
@@ -46,14 +46,32 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>)
4646
return None;
4747
}
4848

49-
if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
49+
let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
5050
ctx.sema.resolve_path(&path.qualifier()?)
51-
{
52-
let target = path.syntax().text_range();
53-
return add_variant_to_accumulator(acc, ctx, target, e, &name_ref, parent);
54-
}
51+
else {
52+
return None;
53+
};
5554

56-
None
55+
let target = path.syntax().text_range();
56+
let name_ref: &ast::NameRef = &name_ref;
57+
let db = ctx.db();
58+
let InRealFile { file_id, value: enum_node } = e.source(db)?.original_ast_node_rooted(db)?;
59+
60+
acc.add(
61+
AssistId("generate_enum_variant", AssistKind::Generate),
62+
"Generate variant",
63+
target,
64+
|builder| {
65+
let mut editor = builder.make_editor(enum_node.syntax());
66+
let make = SyntaxFactory::new();
67+
let field_list = parent.make_field_list(ctx, &make);
68+
let variant = make.variant(None, make.name(&name_ref.text()), field_list, None);
69+
if let Some(it) = enum_node.variant_list() {
70+
it.add_variant(&mut editor, &variant);
71+
}
72+
builder.add_file_edits(file_id, editor);
73+
},
74+
)
5775
}
5876

5977
#[derive(Debug)]
@@ -65,6 +83,20 @@ enum PathParent {
6583
}
6684

6785
impl PathParent {
86+
fn new(path: &ast::Path) -> Option<Self> {
87+
let parent = path.syntax().parent()?;
88+
89+
match_ast! {
90+
match parent {
91+
ast::PathExpr(it) => Some(PathParent::PathExpr(it)),
92+
ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)),
93+
ast::PathPat(it) => Some(PathParent::PathPat(it)),
94+
ast::UseTree(it) => Some(PathParent::UseTree(it)),
95+
_ => None
96+
}
97+
}
98+
}
99+
68100
fn syntax(&self) -> &SyntaxNode {
69101
match self {
70102
PathParent::PathExpr(it) => it.syntax(),
@@ -74,97 +106,49 @@ impl PathParent {
74106
}
75107
}
76108

77-
fn make_field_list(&self, ctx: &AssistContext<'_>) -> Option<ast::FieldList> {
109+
fn make_field_list(
110+
&self,
111+
ctx: &AssistContext<'_>,
112+
make: &SyntaxFactory,
113+
) -> Option<ast::FieldList> {
78114
let scope = ctx.sema.scope(self.syntax())?;
79115

80116
match self {
81117
PathParent::PathExpr(it) => {
82-
if let Some(call_expr) = it.syntax().parent().and_then(ast::CallExpr::cast) {
83-
make_tuple_field_list(call_expr, ctx, &scope)
84-
} else {
85-
None
86-
}
118+
let call_expr = ast::CallExpr::cast(it.syntax().parent()?)?;
119+
let args = call_expr.arg_list()?.args();
120+
let tuple_fields = args.map(|arg| {
121+
let ty =
122+
expr_ty(ctx, make, arg, &scope).unwrap_or_else(|| make.ty_infer().into());
123+
make.tuple_field(None, ty)
124+
});
125+
Some(make.tuple_field_list(tuple_fields).into())
126+
}
127+
PathParent::RecordExpr(it) => {
128+
let fields = it.record_expr_field_list()?.fields();
129+
let record_fields = fields.map(|field| {
130+
let name = name_from_field(make, &field);
131+
132+
let ty = field
133+
.expr()
134+
.and_then(|it| expr_ty(ctx, make, it, &scope))
135+
.unwrap_or_else(|| make.ty_infer().into());
136+
137+
make.record_field(None, name, ty)
138+
});
139+
Some(make.record_field_list(record_fields).into())
87140
}
88-
PathParent::RecordExpr(it) => make_record_field_list(it, ctx, &scope),
89141
PathParent::UseTree(_) | PathParent::PathPat(_) => None,
90142
}
91143
}
92144
}
93145

94-
fn path_parent(path: &ast::Path) -> Option<PathParent> {
95-
let parent = path.syntax().parent()?;
96-
97-
match_ast! {
98-
match parent {
99-
ast::PathExpr(it) => Some(PathParent::PathExpr(it)),
100-
ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)),
101-
ast::PathPat(it) => Some(PathParent::PathPat(it)),
102-
ast::UseTree(it) => Some(PathParent::UseTree(it)),
103-
_ => None
104-
}
105-
}
106-
}
107-
108-
fn add_variant_to_accumulator(
109-
acc: &mut Assists,
110-
ctx: &AssistContext<'_>,
111-
target: syntax::TextRange,
112-
adt: hir::Enum,
113-
name_ref: &ast::NameRef,
114-
parent: PathParent,
115-
) -> Option<()> {
116-
let db = ctx.db();
117-
let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node_rooted(db)?;
118-
119-
acc.add(
120-
AssistId("generate_enum_variant", AssistKind::Generate),
121-
"Generate variant",
122-
target,
123-
|builder| {
124-
builder.edit_file(file_id.file_id());
125-
let node = builder.make_mut(enum_node);
126-
let variant = make_variant(ctx, name_ref, parent);
127-
if let Some(it) = node.variant_list() {
128-
it.add_variant(variant.clone_for_update())
129-
}
130-
},
131-
)
132-
}
133-
134-
fn make_variant(
135-
ctx: &AssistContext<'_>,
136-
name_ref: &ast::NameRef,
137-
parent: PathParent,
138-
) -> ast::Variant {
139-
let field_list = parent.make_field_list(ctx);
140-
make::variant(make::name(&name_ref.text()), field_list)
141-
}
142-
143-
fn make_record_field_list(
144-
record: &ast::RecordExpr,
145-
ctx: &AssistContext<'_>,
146-
scope: &hir::SemanticsScope<'_>,
147-
) -> Option<ast::FieldList> {
148-
let fields = record.record_expr_field_list()?.fields();
149-
let record_fields = fields.map(|field| {
150-
let name = name_from_field(&field);
151-
152-
let ty = field
153-
.expr()
154-
.and_then(|it| expr_ty(ctx, it, scope))
155-
.unwrap_or_else(make::ty_placeholder);
156-
157-
make::record_field(None, name, ty)
158-
});
159-
Some(make::record_field_list(record_fields).into())
160-
}
161-
162-
fn name_from_field(field: &ast::RecordExprField) -> ast::Name {
146+
fn name_from_field(make: &SyntaxFactory, field: &ast::RecordExprField) -> ast::Name {
163147
let text = match field.name_ref() {
164148
Some(it) => it.to_string(),
165149
None => name_from_field_shorthand(field).unwrap_or("unknown".to_owned()),
166150
};
167-
make::name(&text)
151+
make.name(&text)
168152
}
169153

170154
fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> {
@@ -175,27 +159,15 @@ fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> {
175159
Some(path.as_single_name_ref()?.to_string())
176160
}
177161

178-
fn make_tuple_field_list(
179-
call_expr: ast::CallExpr,
180-
ctx: &AssistContext<'_>,
181-
scope: &hir::SemanticsScope<'_>,
182-
) -> Option<ast::FieldList> {
183-
let args = call_expr.arg_list()?.args();
184-
let tuple_fields = args.map(|arg| {
185-
let ty = expr_ty(ctx, arg, scope).unwrap_or_else(make::ty_placeholder);
186-
make::tuple_field(None, ty)
187-
});
188-
Some(make::tuple_field_list(tuple_fields).into())
189-
}
190-
191162
fn expr_ty(
192163
ctx: &AssistContext<'_>,
164+
make: &SyntaxFactory,
193165
arg: ast::Expr,
194166
scope: &hir::SemanticsScope<'_>,
195167
) -> Option<ast::Type> {
196168
let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?;
197169
let text = ty.display_source_code(ctx.db(), scope.module().into(), false).ok()?;
198-
Some(make::ty(&text))
170+
Some(make.ty(&text))
199171
}
200172

201173
#[cfg(test)]

crates/syntax/src/ast/edit_in_place.rs

-124
Original file line numberDiff line numberDiff line change
@@ -909,30 +909,6 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken {
909909
}
910910
}
911911

912-
impl ast::VariantList {
913-
pub fn add_variant(&self, variant: ast::Variant) {
914-
let (indent, position) = match self.variants().last() {
915-
Some(last_item) => (
916-
IndentLevel::from_node(last_item.syntax()),
917-
Position::after(get_or_insert_comma_after(last_item.syntax())),
918-
),
919-
None => match self.l_curly_token() {
920-
Some(l_curly) => {
921-
normalize_ws_between_braces(self.syntax());
922-
(IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly))
923-
}
924-
None => (IndentLevel::single(), Position::last_child_of(self.syntax())),
925-
},
926-
};
927-
let elements: Vec<SyntaxElement> = vec![
928-
make::tokens::whitespace(&format!("{}{indent}", "\n")).into(),
929-
variant.syntax().clone().into(),
930-
ast::make::token(T![,]).into(),
931-
];
932-
ted::insert_all(position, elements);
933-
}
934-
}
935-
936912
fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
937913
let l = node
938914
.children_with_tokens()
@@ -1055,8 +1031,6 @@ mod tests {
10551031
use std::fmt;
10561032

10571033
use parser::Edition;
1058-
use stdx::trim_indent;
1059-
use test_utils::assert_eq_text;
10601034

10611035
use crate::SourceFile;
10621036

@@ -1170,102 +1144,4 @@ mod tests {
11701144
check("let a: u8 = 3;", "let a = 3;", None);
11711145
check("let a: = 3;", "let a = 3;", None);
11721146
}
1173-
1174-
#[test]
1175-
fn add_variant_to_empty_enum() {
1176-
let variant = make::variant(make::name("Bar"), None).clone_for_update();
1177-
1178-
check_add_variant(
1179-
r#"
1180-
enum Foo {}
1181-
"#,
1182-
r#"
1183-
enum Foo {
1184-
Bar,
1185-
}
1186-
"#,
1187-
variant,
1188-
);
1189-
}
1190-
1191-
#[test]
1192-
fn add_variant_to_non_empty_enum() {
1193-
let variant = make::variant(make::name("Baz"), None).clone_for_update();
1194-
1195-
check_add_variant(
1196-
r#"
1197-
enum Foo {
1198-
Bar,
1199-
}
1200-
"#,
1201-
r#"
1202-
enum Foo {
1203-
Bar,
1204-
Baz,
1205-
}
1206-
"#,
1207-
variant,
1208-
);
1209-
}
1210-
1211-
#[test]
1212-
fn add_variant_with_tuple_field_list() {
1213-
let variant = make::variant(
1214-
make::name("Baz"),
1215-
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(std::iter::once(
1216-
make::tuple_field(None, make::ty("bool")),
1217-
)))),
1218-
)
1219-
.clone_for_update();
1220-
1221-
check_add_variant(
1222-
r#"
1223-
enum Foo {
1224-
Bar,
1225-
}
1226-
"#,
1227-
r#"
1228-
enum Foo {
1229-
Bar,
1230-
Baz(bool),
1231-
}
1232-
"#,
1233-
variant,
1234-
);
1235-
}
1236-
1237-
#[test]
1238-
fn add_variant_with_record_field_list() {
1239-
let variant = make::variant(
1240-
make::name("Baz"),
1241-
Some(ast::FieldList::RecordFieldList(make::record_field_list(std::iter::once(
1242-
make::record_field(None, make::name("x"), make::ty("bool")),
1243-
)))),
1244-
)
1245-
.clone_for_update();
1246-
1247-
check_add_variant(
1248-
r#"
1249-
enum Foo {
1250-
Bar,
1251-
}
1252-
"#,
1253-
r#"
1254-
enum Foo {
1255-
Bar,
1256-
Baz { x: bool },
1257-
}
1258-
"#,
1259-
variant,
1260-
);
1261-
}
1262-
1263-
fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) {
1264-
let enum_ = ast_mut_from_text::<ast::Enum>(before);
1265-
if let Some(it) = enum_.variant_list() {
1266-
it.add_variant(variant)
1267-
}
1268-
let after = enum_.to_string();
1269-
assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim()));
1270-
}
12711147
}

0 commit comments

Comments
 (0)