|
| 1 | +use crate::assist_context::{AssistContext, Assists}; |
| 2 | +use ide_db::{assists::AssistId, SnippetCap}; |
| 3 | +use syntax::{ |
| 4 | + ast::{self, HasGenericParams, HasVisibility}, |
| 5 | + AstNode, |
| 6 | +}; |
| 7 | + |
| 8 | +// NOTES : |
| 9 | +// We generate erroneous code if a function is declared const (E0379) |
| 10 | +// This is left to the user to correct as our only option is to remove the |
| 11 | +// function completely which we should not be doing. |
| 12 | + |
| 13 | +// Assist: generate_trait_from_impl |
| 14 | +// |
| 15 | +// Generate trait for an already defined inherent impl and convert impl to a trait impl. |
| 16 | +// |
| 17 | +// ``` |
| 18 | +// struct Foo<const N: usize>([i32; N]); |
| 19 | +// |
| 20 | +// macro_rules! const_maker { |
| 21 | +// ($t:ty, $v:tt) => { |
| 22 | +// const CONST: $t = $v; |
| 23 | +// }; |
| 24 | +// } |
| 25 | +// |
| 26 | +// impl<const N: usize> Fo$0o<N> { |
| 27 | +// // Used as an associated constant. |
| 28 | +// const CONST_ASSOC: usize = N * 4; |
| 29 | +// |
| 30 | +// fn create() -> Option<()> { |
| 31 | +// Some(()) |
| 32 | +// } |
| 33 | +// |
| 34 | +// const_maker! {i32, 7} |
| 35 | +// } |
| 36 | +// ``` |
| 37 | +// -> |
| 38 | +// ``` |
| 39 | +// struct Foo<const N: usize>([i32; N]); |
| 40 | +// |
| 41 | +// macro_rules! const_maker { |
| 42 | +// ($t:ty, $v:tt) => { |
| 43 | +// const CONST: $t = $v; |
| 44 | +// }; |
| 45 | +// } |
| 46 | +// |
| 47 | +// trait NewTrait<const N: usize> { |
| 48 | +// // Used as an associated constant. |
| 49 | +// const CONST_ASSOC: usize = N * 4; |
| 50 | +// |
| 51 | +// fn create() -> Option<()>; |
| 52 | +// |
| 53 | +// const_maker! {i32, 7} |
| 54 | +// } |
| 55 | +// |
| 56 | +// impl<const N: usize> NewTrait<N> for Foo<N> { |
| 57 | +// // Used as an associated constant. |
| 58 | +// const CONST_ASSOC: usize = N * 4; |
| 59 | +// |
| 60 | +// fn create() -> Option<()> { |
| 61 | +// Some(()) |
| 62 | +// } |
| 63 | +// |
| 64 | +// const_maker! {i32, 7} |
| 65 | +// } |
| 66 | +// ``` |
| 67 | +pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { |
| 68 | + // Get AST Node |
| 69 | + let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?; |
| 70 | + |
| 71 | + // If impl is not inherent then we don't really need to go any further. |
| 72 | + if impl_ast.for_token().is_some() { |
| 73 | + return None; |
| 74 | + } |
| 75 | + |
| 76 | + let assoc_items = impl_ast.assoc_item_list(); |
| 77 | + if assoc_items.is_none() { |
| 78 | + // Also do not do anything if no assoc item is there. |
| 79 | + return None; |
| 80 | + } |
| 81 | + |
| 82 | + let assoc_items = assoc_items.unwrap(); |
| 83 | + let first_element = assoc_items.assoc_items().next(); |
| 84 | + if first_element.is_none() { |
| 85 | + // No reason for an assist. |
| 86 | + return None; |
| 87 | + } |
| 88 | + |
| 89 | + acc.add( |
| 90 | + AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate), |
| 91 | + "Generate trait from impl".to_owned(), |
| 92 | + impl_ast.syntax().text_range(), |
| 93 | + |builder| { |
| 94 | + let trait_items = assoc_items.clone_for_update(); |
| 95 | + let impl_items = assoc_items.clone_for_update(); |
| 96 | + |
| 97 | + trait_items.assoc_items().for_each(|item| { |
| 98 | + strip_body(&item); |
| 99 | + remove_items_visibility(&item); |
| 100 | + }); |
| 101 | + |
| 102 | + syntax::ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax()); |
| 103 | + |
| 104 | + impl_items.assoc_items().for_each(|item| { |
| 105 | + remove_items_visibility(&item); |
| 106 | + }); |
| 107 | + |
| 108 | + let trait_ast = ast::make::trait_( |
| 109 | + false, |
| 110 | + "NewTrait".to_string(), |
| 111 | + HasGenericParams::generic_param_list(&impl_ast), |
| 112 | + HasGenericParams::where_clause(&impl_ast), |
| 113 | + trait_items, |
| 114 | + ); |
| 115 | + |
| 116 | + // Change `impl Foo` to `impl NewTrait for Foo` |
| 117 | + // First find the PATH_TYPE which is what Foo is. |
| 118 | + let impl_name = impl_ast.self_ty().unwrap(); |
| 119 | + let trait_name = if let Some(genpars) = impl_ast.generic_param_list() { |
| 120 | + format!("NewTrait{}", genpars.to_generic_args()) |
| 121 | + } else { |
| 122 | + format!("NewTrait") |
| 123 | + }; |
| 124 | + |
| 125 | + // // Then replace |
| 126 | + builder.replace( |
| 127 | + impl_name.clone().syntax().text_range(), |
| 128 | + format!("{} for {}", trait_name, impl_name.to_string()), |
| 129 | + ); |
| 130 | + |
| 131 | + builder.replace( |
| 132 | + impl_ast.assoc_item_list().unwrap().syntax().text_range(), |
| 133 | + impl_items.to_string(), |
| 134 | + ); |
| 135 | + |
| 136 | + // Insert trait before TraitImpl |
| 137 | + builder.insert_snippet( |
| 138 | + SnippetCap::new(true).unwrap(), |
| 139 | + impl_ast.syntax().text_range().start(), |
| 140 | + format!("{}\n\n", trait_ast.to_string()), |
| 141 | + ); |
| 142 | + }, |
| 143 | + ); |
| 144 | + |
| 145 | + Some(()) |
| 146 | +} |
| 147 | + |
| 148 | +/// `E0449` Trait items always share the visibility of their trait |
| 149 | +fn remove_items_visibility(item: &ast::AssocItem) { |
| 150 | + match item { |
| 151 | + ast::AssocItem::Const(c) => { |
| 152 | + if let Some(vis) = c.visibility() { |
| 153 | + syntax::ted::remove(vis.syntax()); |
| 154 | + } |
| 155 | + } |
| 156 | + ast::AssocItem::Fn(f) => { |
| 157 | + if let Some(vis) = f.visibility() { |
| 158 | + syntax::ted::remove(vis.syntax()); |
| 159 | + } |
| 160 | + } |
| 161 | + ast::AssocItem::TypeAlias(t) => { |
| 162 | + if let Some(vis) = t.visibility() { |
| 163 | + syntax::ted::remove(vis.syntax()); |
| 164 | + } |
| 165 | + } |
| 166 | + _ => (), |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +fn strip_body(item: &ast::AssocItem) { |
| 171 | + match item { |
| 172 | + ast::AssocItem::Fn(f) => { |
| 173 | + if let Some(body) = f.body() { |
| 174 | + // In constrast to function bodies, we want to see no ws before a semicolon. |
| 175 | + // So let's remove them if we see any. |
| 176 | + if let Some(prev) = body.syntax().prev_sibling_or_token() { |
| 177 | + if prev.kind() == syntax::SyntaxKind::WHITESPACE { |
| 178 | + syntax::ted::remove(prev); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + syntax::ted::replace(body.syntax(), ast::make::tokens::semicolon()); |
| 183 | + } |
| 184 | + } |
| 185 | + _ => (), |
| 186 | + }; |
| 187 | +} |
| 188 | + |
| 189 | +#[cfg(test)] |
| 190 | +mod tests { |
| 191 | + use super::*; |
| 192 | + use crate::tests::{check_assist, check_assist_not_applicable}; |
| 193 | + |
| 194 | + #[test] |
| 195 | + fn test_assoc_item_fn() { |
| 196 | + check_assist( |
| 197 | + generate_trait_from_impl, |
| 198 | + r#" |
| 199 | +struct Foo(f64); |
| 200 | +
|
| 201 | +impl F$0oo { |
| 202 | + fn add(&mut self, x: f64) { |
| 203 | + self.0 += x; |
| 204 | + } |
| 205 | +}"#, |
| 206 | + r#" |
| 207 | +struct Foo(f64); |
| 208 | +
|
| 209 | +trait NewTrait { |
| 210 | + fn add(&mut self, x: f64); |
| 211 | +} |
| 212 | +
|
| 213 | +impl NewTrait for Foo { |
| 214 | + fn add(&mut self, x: f64) { |
| 215 | + self.0 += x; |
| 216 | + } |
| 217 | +}"#, |
| 218 | + ) |
| 219 | + } |
| 220 | + |
| 221 | + #[test] |
| 222 | + fn test_assoc_item_macro() { |
| 223 | + check_assist( |
| 224 | + generate_trait_from_impl, |
| 225 | + r#" |
| 226 | +struct Foo; |
| 227 | +
|
| 228 | +macro_rules! const_maker { |
| 229 | + ($t:ty, $v:tt) => { |
| 230 | + const CONST: $t = $v; |
| 231 | + }; |
| 232 | +} |
| 233 | +
|
| 234 | +impl F$0oo { |
| 235 | + const_maker! {i32, 7} |
| 236 | +}"#, |
| 237 | + r#" |
| 238 | +struct Foo; |
| 239 | +
|
| 240 | +macro_rules! const_maker { |
| 241 | + ($t:ty, $v:tt) => { |
| 242 | + const CONST: $t = $v; |
| 243 | + }; |
| 244 | +} |
| 245 | +
|
| 246 | +trait NewTrait { |
| 247 | + const_maker! {i32, 7} |
| 248 | +} |
| 249 | +
|
| 250 | +impl NewTrait for Foo { |
| 251 | + const_maker! {i32, 7} |
| 252 | +}"#, |
| 253 | + ) |
| 254 | + } |
| 255 | + |
| 256 | + #[test] |
| 257 | + fn test_assoc_item_const() { |
| 258 | + check_assist( |
| 259 | + generate_trait_from_impl, |
| 260 | + r#" |
| 261 | +struct Foo; |
| 262 | +
|
| 263 | +impl F$0oo { |
| 264 | + const ABC: i32 = 3; |
| 265 | +}"#, |
| 266 | + r#" |
| 267 | +struct Foo; |
| 268 | +
|
| 269 | +trait NewTrait { |
| 270 | + const ABC: i32 = 3; |
| 271 | +} |
| 272 | +
|
| 273 | +impl NewTrait for Foo { |
| 274 | + const ABC: i32 = 3; |
| 275 | +}"#, |
| 276 | + ) |
| 277 | + } |
| 278 | + |
| 279 | + #[test] |
| 280 | + fn test_impl_with_generics() { |
| 281 | + check_assist( |
| 282 | + generate_trait_from_impl, |
| 283 | + r#" |
| 284 | +struct Foo<const N: usize>([i32; N]); |
| 285 | +
|
| 286 | +impl<const N: usize> F$0oo<N> { |
| 287 | + // Used as an associated constant. |
| 288 | + const CONST: usize = N * 4; |
| 289 | +} |
| 290 | + "#, |
| 291 | + r#" |
| 292 | +struct Foo<const N: usize>([i32; N]); |
| 293 | +
|
| 294 | +trait NewTrait<const N: usize> { |
| 295 | + // Used as an associated constant. |
| 296 | + const CONST: usize = N * 4; |
| 297 | +} |
| 298 | +
|
| 299 | +impl<const N: usize> NewTrait<N> for Foo<N> { |
| 300 | + // Used as an associated constant. |
| 301 | + const CONST: usize = N * 4; |
| 302 | +} |
| 303 | + "#, |
| 304 | + ) |
| 305 | + } |
| 306 | + |
| 307 | + #[test] |
| 308 | + fn test_e0449_avoided() { |
| 309 | + check_assist( |
| 310 | + generate_trait_from_impl, |
| 311 | + r#" |
| 312 | +struct Foo; |
| 313 | +
|
| 314 | +impl F$0oo { |
| 315 | + pub fn a_func() -> Option<()> { |
| 316 | + Some(()) |
| 317 | + } |
| 318 | +}"#, |
| 319 | + r#" |
| 320 | +struct Foo; |
| 321 | +
|
| 322 | +trait NewTrait { |
| 323 | + fn a_func() -> Option<()>; |
| 324 | +} |
| 325 | +
|
| 326 | +impl NewTrait for Foo { |
| 327 | + fn a_func() -> Option<()> { |
| 328 | + Some(()) |
| 329 | + } |
| 330 | +}"#, |
| 331 | + ) |
| 332 | + } |
| 333 | + |
| 334 | + #[test] |
| 335 | + fn test_empty_inherent_impl() { |
| 336 | + check_assist_not_applicable( |
| 337 | + generate_trait_from_impl, |
| 338 | + r#" |
| 339 | +impl Emp$0tyImpl{} |
| 340 | +"#, |
| 341 | + ) |
| 342 | + } |
| 343 | +} |
0 commit comments