aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs152
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs148
-rw-r--r--crates/ide/src/diagnostics/fixes.rs3
-rw-r--r--crates/syntax/src/ast/make.rs27
4 files changed, 251 insertions, 79 deletions
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 8757fa33f..669dd9b21 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,13 +1,16 @@
1use ide_db::imports_locator;
1use itertools::Itertools; 2use itertools::Itertools;
2use syntax::{ 3use syntax::{
3 ast::{self, AstNode}, 4 ast::{self, make, AstNode},
4 Direction, SmolStr, 5 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE}, 6 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize, 7 TextRange, TextSize,
7}; 8};
8 9
9use crate::{ 10use crate::{
10 assist_context::{AssistContext, Assists}, 11 assist_config::SnippetCap,
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::mod_path_to_ast,
11 AssistId, AssistKind, 14 AssistId, AssistKind,
12}; 15};
13 16
@@ -30,72 +33,116 @@ use crate::{
30// ``` 33// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 35 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34 36
35 let attr_name = attr 37 let attr_name = attr
36 .syntax() 38 .syntax()
37 .descendants_with_tokens() 39 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT) 40 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token()) 41 .find_map(syntax::NodeOrToken::into_token)
40 .filter(|t| *t.text() == "derive")? 42 .filter(|t| t.text() == "derive")?
41 .text() 43 .text()
42 .clone(); 44 .clone();
43 45
44 let trait_token = 46 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; 47 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
48 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
46 49
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 50 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string(); 51 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end(); 52 let insert_pos = annotated.syntax().parent()?.text_range().end();
53
54 let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55 let current_crate = current_module.krate();
56
57 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
58 .into_iter()
59 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
60 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
61 _ => None,
62 })
63 .flat_map(|trait_| {
64 current_module
65 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
66 .as_ref()
67 .map(mod_path_to_ast)
68 .zip(Some(trait_))
69 });
50 70
51 let label = 71 let mut no_traits_found = true;
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 72 for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
74 }
75 if no_traits_found {
76 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77 }
78 Some(())
79}
53 80
81fn add_assist(
82 acc: &mut Assists,
83 snippet_cap: Option<SnippetCap>,
84 attr: &ast::Attr,
85 trait_path: &ast::Path,
86 annotated_name: &str,
87 insert_pos: TextSize,
88) -> Option<()> {
54 let target = attr.syntax().text_range(); 89 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { 90 let input = attr.token_tree()?;
56 let new_attr_input = input 91 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
57 .syntax() 92 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64
65 if has_more_derives {
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80 93
81 match ctx.config.snippet_cap { 94 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
95 update_attribute(builder, &input, &trait_name, &attr);
96 match snippet_cap {
82 Some(cap) => { 97 Some(cap) => {
83 builder.insert_snippet( 98 builder.insert_snippet(
84 cap, 99 cap,
85 start_offset, 100 insert_pos,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), 101 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
87 ); 102 );
88 } 103 }
89 None => { 104 None => {
90 builder.insert( 105 builder.insert(
91 start_offset, 106 insert_pos,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), 107 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
93 ); 108 );
94 } 109 }
95 } 110 }
96 }) 111 })
97} 112}
98 113
114fn update_attribute(
115 builder: &mut AssistBuilder,
116 input: &ast::TokenTree,
117 trait_name: &ast::NameRef,
118 attr: &ast::Attr,
119) {
120 let new_attr_input = input
121 .syntax()
122 .descendants_with_tokens()
123 .filter(|t| t.kind() == IDENT)
124 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
125 .filter(|t| t != trait_name.text())
126 .collect::<Vec<SmolStr>>();
127 let has_more_derives = !new_attr_input.is_empty();
128
129 if has_more_derives {
130 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
131 builder.replace(input.syntax().text_range(), new_attr_input);
132 } else {
133 let attr_range = attr.syntax().text_range();
134 builder.delete(attr_range);
135
136 let line_break_range = attr
137 .syntax()
138 .next_sibling_or_token()
139 .filter(|t| t.kind() == WHITESPACE)
140 .map(|t| t.text_range())
141 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142 builder.delete(line_break_range);
143 }
144}
145
99#[cfg(test)] 146#[cfg(test)]
100mod tests { 147mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable}; 148 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -103,6 +150,35 @@ mod tests {
103 use super::*; 150 use super::*;
104 151
105 #[test] 152 #[test]
153 fn add_custom_impl_qualified() {
154 check_assist(
155 add_custom_impl,
156 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl fmt::Debug for Foo {
176 $0
177}
178",
179 )
180 }
181 #[test]
106 fn add_custom_impl_for_unique_input() { 182 fn add_custom_impl_for_unique_input() {
107 check_assist( 183 check_assist(
108 add_custom_impl, 184 add_custom_impl,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index dddab255e..14209b771 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -1,3 +1,6 @@
1use std::iter;
2
3use either::Either;
1use hir::{AsName, EnumVariant, Module, ModuleDef, Name}; 4use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
2use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
3use rustc_hash::{FxHashMap, FxHashSet}; 6use rustc_hash::{FxHashMap, FxHashSet};
@@ -31,40 +34,32 @@ pub(crate) fn extract_struct_from_enum_variant(
31 ctx: &AssistContext, 34 ctx: &AssistContext,
32) -> Option<()> { 35) -> Option<()> {
33 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 36 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
34 let field_list = match variant.kind() { 37 let field_list = extract_field_list_if_applicable(&variant)?;
35 ast::StructKind::Tuple(field_list) => field_list,
36 _ => return None,
37 };
38
39 // skip 1-tuple variants
40 if field_list.fields().count() == 1 {
41 return None;
42 }
43 38
44 let variant_name = variant.name()?; 39 let variant_name = variant.name()?;
45 let variant_hir = ctx.sema.to_def(&variant)?; 40 let variant_hir = ctx.sema.to_def(&variant)?;
46 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { 41 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
47 return None; 42 return None;
48 } 43 }
44
49 let enum_ast = variant.parent_enum(); 45 let enum_ast = variant.parent_enum();
50 let visibility = enum_ast.visibility();
51 let enum_hir = ctx.sema.to_def(&enum_ast)?; 46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
52 let variant_hir_name = variant_hir.name(ctx.db());
53 let enum_module_def = ModuleDef::from(enum_hir);
54 let current_module = enum_hir.module(ctx.db());
55 let target = variant.syntax().text_range(); 47 let target = variant.syntax().text_range();
56 acc.add( 48 acc.add(
57 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), 49 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
58 "Extract struct from enum variant", 50 "Extract struct from enum variant",
59 target, 51 target,
60 |builder| { 52 |builder| {
61 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); 53 let variant_hir_name = variant_hir.name(ctx.db());
62 let res = definition.usages(&ctx.sema).all(); 54 let enum_module_def = ModuleDef::from(enum_hir);
55 let usages =
56 Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)).usages(&ctx.sema).all();
63 57
64 let mut visited_modules_set = FxHashSet::default(); 58 let mut visited_modules_set = FxHashSet::default();
59 let current_module = enum_hir.module(ctx.db());
65 visited_modules_set.insert(current_module); 60 visited_modules_set.insert(current_module);
66 let mut rewriters = FxHashMap::default(); 61 let mut rewriters = FxHashMap::default();
67 for reference in res { 62 for reference in usages {
68 let rewriter = rewriters 63 let rewriter = rewriters
69 .entry(reference.file_range.file_id) 64 .entry(reference.file_range.file_id)
70 .or_insert_with(SyntaxRewriter::default); 65 .or_insert_with(SyntaxRewriter::default);
@@ -86,26 +81,49 @@ pub(crate) fn extract_struct_from_enum_variant(
86 builder.rewrite(rewriter); 81 builder.rewrite(rewriter);
87 } 82 }
88 builder.edit_file(ctx.frange.file_id); 83 builder.edit_file(ctx.frange.file_id);
89 update_variant(&mut rewriter, &variant_name, &field_list); 84 update_variant(&mut rewriter, &variant);
90 extract_struct_def( 85 extract_struct_def(
91 &mut rewriter, 86 &mut rewriter,
92 &enum_ast, 87 &enum_ast,
93 variant_name.clone(), 88 variant_name.clone(),
94 &field_list, 89 &field_list,
95 &variant.parent_enum().syntax().clone().into(), 90 &variant.parent_enum().syntax().clone().into(),
96 visibility, 91 enum_ast.visibility(),
97 ); 92 );
98 builder.rewrite(rewriter); 93 builder.rewrite(rewriter);
99 }, 94 },
100 ) 95 )
101} 96}
102 97
103fn existing_struct_def(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool { 98fn extract_field_list_if_applicable(
99 variant: &ast::Variant,
100) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
101 match variant.kind() {
102 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
103 Some(Either::Left(field_list))
104 }
105 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
106 Some(Either::Right(field_list))
107 }
108 _ => None,
109 }
110}
111
112fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
104 variant 113 variant
105 .parent_enum(db) 114 .parent_enum(db)
106 .module(db) 115 .module(db)
107 .scope(db, None) 116 .scope(db, None)
108 .into_iter() 117 .into_iter()
118 .filter(|(_, def)| match def {
119 // only check type-namespace
120 hir::ScopeDef::ModuleDef(def) => matches!(def,
121 ModuleDef::Module(_) | ModuleDef::Adt(_) |
122 ModuleDef::EnumVariant(_) | ModuleDef::Trait(_) |
123 ModuleDef::TypeAlias(_) | ModuleDef::BuiltinType(_)
124 ),
125 _ => false,
126 })
109 .any(|(name, _)| name == variant_name.as_name()) 127 .any(|(name, _)| name == variant_name.as_name())
110} 128}
111 129
@@ -133,19 +151,29 @@ fn extract_struct_def(
133 rewriter: &mut SyntaxRewriter, 151 rewriter: &mut SyntaxRewriter,
134 enum_: &ast::Enum, 152 enum_: &ast::Enum,
135 variant_name: ast::Name, 153 variant_name: ast::Name,
136 variant_list: &ast::TupleFieldList, 154 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
137 start_offset: &SyntaxElement, 155 start_offset: &SyntaxElement,
138 visibility: Option<ast::Visibility>, 156 visibility: Option<ast::Visibility>,
139) -> Option<()> { 157) -> Option<()> {
140 let variant_list = make::tuple_field_list( 158 let pub_vis = Some(make::visibility_pub());
141 variant_list 159 let field_list = match field_list {
142 .fields() 160 Either::Left(field_list) => {
143 .flat_map(|field| Some(make::tuple_field(Some(make::visibility_pub()), field.ty()?))), 161 make::record_field_list(field_list.fields().flat_map(|field| {
144 ); 162 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
163 }))
164 .into()
165 }
166 Either::Right(field_list) => make::tuple_field_list(
167 field_list
168 .fields()
169 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
170 )
171 .into(),
172 };
145 173
146 rewriter.insert_before( 174 rewriter.insert_before(
147 start_offset, 175 start_offset,
148 make::struct_(visibility, variant_name, None, variant_list.into()).syntax(), 176 make::struct_(visibility, variant_name, None, field_list).syntax(),
149 ); 177 );
150 rewriter.insert_before(start_offset, &make::tokens::blank_line()); 178 rewriter.insert_before(start_offset, &make::tokens::blank_line());
151 179
@@ -156,15 +184,14 @@ fn extract_struct_def(
156 Some(()) 184 Some(())
157} 185}
158 186
159fn update_variant( 187fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
160 rewriter: &mut SyntaxRewriter, 188 let name = variant.name()?;
161 variant_name: &ast::Name, 189 let tuple_field = make::tuple_field(None, make::ty(name.text()));
162 field_list: &ast::TupleFieldList, 190 let replacement = make::variant(
163) -> Option<()> { 191 name,
164 let (l, r): (SyntaxElement, SyntaxElement) = 192 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
165 (field_list.l_paren_token()?.into(), field_list.r_paren_token()?.into()); 193 );
166 let replacement = vec![l, variant_name.syntax().clone().into(), r]; 194 rewriter.replace(variant.syntax(), replacement.syntax());
167 rewriter.replace_with_many(field_list.syntax(), replacement);
168 Some(()) 195 Some(())
169} 196}
170 197
@@ -211,7 +238,7 @@ mod tests {
211 use super::*; 238 use super::*;
212 239
213 #[test] 240 #[test]
214 fn test_extract_struct_several_fields() { 241 fn test_extract_struct_several_fields_tuple() {
215 check_assist( 242 check_assist(
216 extract_struct_from_enum_variant, 243 extract_struct_from_enum_variant,
217 "enum A { <|>One(u32, u32) }", 244 "enum A { <|>One(u32, u32) }",
@@ -222,6 +249,41 @@ enum A { One(One) }"#,
222 } 249 }
223 250
224 #[test] 251 #[test]
252 fn test_extract_struct_several_fields_named() {
253 check_assist(
254 extract_struct_from_enum_variant,
255 "enum A { <|>One { foo: u32, bar: u32 } }",
256 r#"struct One{ pub foo: u32, pub bar: u32 }
257
258enum A { One(One) }"#,
259 );
260 }
261
262 #[test]
263 fn test_extract_struct_one_field_named() {
264 check_assist(
265 extract_struct_from_enum_variant,
266 "enum A { <|>One { foo: u32 } }",
267 r#"struct One{ pub foo: u32 }
268
269enum A { One(One) }"#,
270 );
271 }
272
273 #[test]
274 fn test_extract_enum_variant_name_value_namespace() {
275 check_assist(
276 extract_struct_from_enum_variant,
277 r#"const One: () = ();
278enum A { <|>One(u32, u32) }"#,
279 r#"const One: () = ();
280struct One(pub u32, pub u32);
281
282enum A { One(One) }"#,
283 );
284 }
285
286 #[test]
225 fn test_extract_struct_pub_visibility() { 287 fn test_extract_struct_pub_visibility() {
226 check_assist( 288 check_assist(
227 extract_struct_from_enum_variant, 289 extract_struct_from_enum_variant,
@@ -298,7 +360,7 @@ fn another_fn() {
298 fn test_extract_enum_not_applicable_if_struct_exists() { 360 fn test_extract_enum_not_applicable_if_struct_exists() {
299 check_not_applicable( 361 check_not_applicable(
300 r#"struct One; 362 r#"struct One;
301 enum A { <|>One(u8) }"#, 363 enum A { <|>One(u8, u32) }"#,
302 ); 364 );
303 } 365 }
304 366
@@ -306,4 +368,14 @@ fn another_fn() {
306 fn test_extract_not_applicable_one_field() { 368 fn test_extract_not_applicable_one_field() {
307 check_not_applicable(r"enum A { <|>One(u32) }"); 369 check_not_applicable(r"enum A { <|>One(u32) }");
308 } 370 }
371
372 #[test]
373 fn test_extract_not_applicable_no_field_tuple() {
374 check_not_applicable(r"enum A { <|>None() }");
375 }
376
377 #[test]
378 fn test_extract_not_applicable_no_field_named() {
379 check_not_applicable(r"enum A { <|>None {} }");
380 }
309} 381}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 02e17ba43..d275dd75b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -157,7 +157,8 @@ fn missing_record_expr_field_fix(
157 return None; 157 return None;
158 } 158 }
159 let new_field = make::record_field( 159 let new_field = make::record_field(
160 record_expr_field.field_name()?, 160 None,
161 make::name(record_expr_field.field_name()?.text()),
161 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), 162 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
162 ); 163 );
163 164
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 2cf436e7a..b1578820f 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -110,8 +110,16 @@ pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::Re
110 } 110 }
111} 111}
112 112
113pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { 113pub fn record_field(
114 ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) 114 visibility: Option<ast::Visibility>,
115 name: ast::Name,
116 ty: ast::Type,
117) -> ast::RecordField {
118 let visibility = match visibility {
119 None => String::new(),
120 Some(it) => format!("{} ", it),
121 };
122 ast_from_text(&format!("struct S {{ {}{}: {}, }}", visibility, name, ty))
115} 123}
116 124
117pub fn block_expr( 125pub fn block_expr(
@@ -360,6 +368,13 @@ pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> as
360 ast_from_text(&format!("struct f({});", fields)) 368 ast_from_text(&format!("struct f({});", fields))
361} 369}
362 370
371pub fn record_field_list(
372 fields: impl IntoIterator<Item = ast::RecordField>,
373) -> ast::RecordFieldList {
374 let fields = fields.into_iter().join(", ");
375 ast_from_text(&format!("struct f {{ {} }}", fields))
376}
377
363pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField { 378pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField {
364 let visibility = match visibility { 379 let visibility = match visibility {
365 None => String::new(), 380 None => String::new(),
@@ -368,6 +383,14 @@ pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::T
368 ast_from_text(&format!("struct f({}{});", visibility, ty)) 383 ast_from_text(&format!("struct f({}{});", visibility, ty))
369} 384}
370 385
386pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant {
387 let field_list = match field_list {
388 None => String::new(),
389 Some(it) => format!("{}", it),
390 };
391 ast_from_text(&format!("enum f {{ {}{} }}", name, field_list))
392}
393
371pub fn fn_( 394pub fn fn_(
372 visibility: Option<ast::Visibility>, 395 visibility: Option<ast::Visibility>,
373 fn_name: ast::Name, 396 fn_name: ast::Name,