diff options
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/ast_transform.rs | 28 | ||||
-rw-r--r-- | crates/assists/src/handlers/add_missing_impl_members.rs | 75 | ||||
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 33 | ||||
-rw-r--r-- | crates/assists/src/handlers/invert_if.rs | 13 | ||||
-rw-r--r-- | crates/assists/src/handlers/merge_imports.rs | 14 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_qualified_name_with_use.rs | 20 | ||||
-rw-r--r-- | crates/assists/src/utils/insert_use.rs | 346 |
7 files changed, 409 insertions, 120 deletions
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs index bbcd2d488..835da3bb2 100644 --- a/crates/assists/src/ast_transform.rs +++ b/crates/assists/src/ast_transform.rs | |||
@@ -18,6 +18,34 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | |||
18 | .rewrite_ast(&node) | 18 | .rewrite_ast(&node) |
19 | } | 19 | } |
20 | 20 | ||
21 | /// `AstTransform` helps with applying bulk transformations to syntax nodes. | ||
22 | /// | ||
23 | /// This is mostly useful for IDE code generation. If you paste some existing | ||
24 | /// code into a new context (for example, to add method overrides to an `impl` | ||
25 | /// block), you generally want to appropriately qualify the names, and sometimes | ||
26 | /// you might want to substitute generic parameters as well: | ||
27 | /// | ||
28 | /// ``` | ||
29 | /// mod x { | ||
30 | /// pub struct A; | ||
31 | /// pub trait T<U> { fn foo(&self, _: U) -> A; } | ||
32 | /// } | ||
33 | /// | ||
34 | /// mod y { | ||
35 | /// use x::T; | ||
36 | /// | ||
37 | /// impl T<()> for () { | ||
38 | /// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`. | ||
39 | /// // But we want a slightly-modified version of it: | ||
40 | /// fn foo(&self, _: ()) -> x::A {} | ||
41 | /// } | ||
42 | /// } | ||
43 | /// ``` | ||
44 | /// | ||
45 | /// So, a single `AstTransform` describes such function from `SyntaxNode` to | ||
46 | /// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow. | ||
47 | /// We'd want to somehow express this concept simpler, but so far nobody got to | ||
48 | /// simplifying this! | ||
21 | pub trait AstTransform<'a> { | 49 | pub trait AstTransform<'a> { |
22 | fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; | 50 | fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; |
23 | 51 | ||
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs index 83a2ada9a..1ac5fefd6 100644 --- a/crates/assists/src/handlers/add_missing_impl_members.rs +++ b/crates/assists/src/handlers/add_missing_impl_members.rs | |||
@@ -111,8 +111,6 @@ fn add_missing_impl_members_inner( | |||
111 | ) -> Option<()> { | 111 | ) -> Option<()> { |
112 | let _p = profile::span("add_missing_impl_members_inner"); | 112 | let _p = profile::span("add_missing_impl_members_inner"); |
113 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; | 113 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; |
114 | let impl_item_list = impl_def.assoc_item_list()?; | ||
115 | |||
116 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; | 114 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; |
117 | 115 | ||
118 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { | 116 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { |
@@ -148,11 +146,14 @@ fn add_missing_impl_members_inner( | |||
148 | 146 | ||
149 | let target = impl_def.syntax().text_range(); | 147 | let target = impl_def.syntax().text_range(); |
150 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { | 148 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { |
149 | let impl_item_list = impl_def.assoc_item_list().unwrap_or(make::assoc_item_list()); | ||
150 | |||
151 | let n_existing_items = impl_item_list.assoc_items().count(); | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
152 | let source_scope = ctx.sema.scope_for_def(trait_); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); | 153 | let target_scope = ctx.sema.scope(impl_def.syntax()); |
154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | 154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) |
155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); | 155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); |
156 | |||
156 | let items = missing_items | 157 | let items = missing_items |
157 | .into_iter() | 158 | .into_iter() |
158 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 159 | .map(|it| ast_transform::apply(&*ast_transform, it)) |
@@ -162,12 +163,14 @@ fn add_missing_impl_members_inner( | |||
162 | _ => it, | 163 | _ => it, |
163 | }) | 164 | }) |
164 | .map(|it| edit::remove_attrs_and_docs(&it)); | 165 | .map(|it| edit::remove_attrs_and_docs(&it)); |
166 | |||
165 | let new_impl_item_list = impl_item_list.append_items(items); | 167 | let new_impl_item_list = impl_item_list.append_items(items); |
166 | let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); | 168 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); |
169 | let first_new_item = | ||
170 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
167 | 171 | ||
168 | let original_range = impl_item_list.syntax().text_range(); | ||
169 | match ctx.config.snippet_cap { | 172 | match ctx.config.snippet_cap { |
170 | None => builder.replace(original_range, new_impl_item_list.to_string()), | 173 | None => builder.replace(target, new_impl_def.to_string()), |
171 | Some(cap) => { | 174 | Some(cap) => { |
172 | let mut cursor = Cursor::Before(first_new_item.syntax()); | 175 | let mut cursor = Cursor::Before(first_new_item.syntax()); |
173 | let placeholder; | 176 | let placeholder; |
@@ -181,8 +184,8 @@ fn add_missing_impl_members_inner( | |||
181 | } | 184 | } |
182 | builder.replace_snippet( | 185 | builder.replace_snippet( |
183 | cap, | 186 | cap, |
184 | original_range, | 187 | target, |
185 | render_snippet(cap, new_impl_item_list.syntax(), cursor), | 188 | render_snippet(cap, new_impl_def.syntax(), cursor), |
186 | ) | 189 | ) |
187 | } | 190 | } |
188 | }; | 191 | }; |
@@ -311,6 +314,25 @@ impl Foo for S { | |||
311 | } | 314 | } |
312 | 315 | ||
313 | #[test] | 316 | #[test] |
317 | fn test_impl_def_without_braces() { | ||
318 | check_assist( | ||
319 | add_missing_impl_members, | ||
320 | r#" | ||
321 | trait Foo { fn foo(&self); } | ||
322 | struct S; | ||
323 | impl Foo for S<|>"#, | ||
324 | r#" | ||
325 | trait Foo { fn foo(&self); } | ||
326 | struct S; | ||
327 | impl Foo for S { | ||
328 | fn foo(&self) { | ||
329 | ${0:todo!()} | ||
330 | } | ||
331 | }"#, | ||
332 | ); | ||
333 | } | ||
334 | |||
335 | #[test] | ||
314 | fn fill_in_type_params_1() { | 336 | fn fill_in_type_params_1() { |
315 | check_assist( | 337 | check_assist( |
316 | add_missing_impl_members, | 338 | add_missing_impl_members, |
@@ -393,6 +415,41 @@ impl foo::Foo for S { | |||
393 | } | 415 | } |
394 | 416 | ||
395 | #[test] | 417 | #[test] |
418 | fn test_qualify_path_2() { | ||
419 | check_assist( | ||
420 | add_missing_impl_members, | ||
421 | r#" | ||
422 | mod foo { | ||
423 | pub mod bar { | ||
424 | pub struct Bar; | ||
425 | pub trait Foo { fn foo(&self, bar: Bar); } | ||
426 | } | ||
427 | } | ||
428 | |||
429 | use foo::bar; | ||
430 | |||
431 | struct S; | ||
432 | impl bar::Foo for S { <|> }"#, | ||
433 | r#" | ||
434 | mod foo { | ||
435 | pub mod bar { | ||
436 | pub struct Bar; | ||
437 | pub trait Foo { fn foo(&self, bar: Bar); } | ||
438 | } | ||
439 | } | ||
440 | |||
441 | use foo::bar; | ||
442 | |||
443 | struct S; | ||
444 | impl bar::Foo for S { | ||
445 | fn foo(&self, bar: bar::Bar) { | ||
446 | ${0:todo!()} | ||
447 | } | ||
448 | }"#, | ||
449 | ); | ||
450 | } | ||
451 | |||
452 | #[test] | ||
396 | fn test_qualify_path_generic() { | 453 | fn test_qualify_path_generic() { |
397 | check_assist( | 454 | check_assist( |
398 | add_missing_impl_members, | 455 | add_missing_impl_members, |
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 5a1b5a4ac..fa524ffd9 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -192,10 +192,10 @@ impl AutoImportAssets { | |||
192 | }) | 192 | }) |
193 | .filter_map(|candidate| match candidate { | 193 | .filter_map(|candidate| match candidate { |
194 | Either::Left(module_def) => { | 194 | Either::Left(module_def) => { |
195 | self.module_with_name_to_import.find_use_path(db, module_def) | 195 | self.module_with_name_to_import.find_use_path_prefixed(db, module_def) |
196 | } | 196 | } |
197 | Either::Right(macro_def) => { | 197 | Either::Right(macro_def) => { |
198 | self.module_with_name_to_import.find_use_path(db, macro_def) | 198 | self.module_with_name_to_import.find_use_path_prefixed(db, macro_def) |
199 | } | 199 | } |
200 | }) | 200 | }) |
201 | .filter(|use_path| !use_path.segments.is_empty()) | 201 | .filter(|use_path| !use_path.segments.is_empty()) |
@@ -287,6 +287,35 @@ mod tests { | |||
287 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 287 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
288 | 288 | ||
289 | #[test] | 289 | #[test] |
290 | fn applicable_when_found_an_import_partial() { | ||
291 | check_assist( | ||
292 | auto_import, | ||
293 | r" | ||
294 | mod std { | ||
295 | pub mod fmt { | ||
296 | pub struct Formatter; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | use std::fmt; | ||
301 | |||
302 | <|>Formatter | ||
303 | ", | ||
304 | r" | ||
305 | mod std { | ||
306 | pub mod fmt { | ||
307 | pub struct Formatter; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | use std::fmt::{self, Formatter}; | ||
312 | |||
313 | Formatter | ||
314 | ", | ||
315 | ); | ||
316 | } | ||
317 | |||
318 | #[test] | ||
290 | fn applicable_when_found_an_import() { | 319 | fn applicable_when_found_an_import() { |
291 | check_assist( | 320 | check_assist( |
292 | auto_import, | 321 | auto_import, |
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs index 294256297..461fcf862 100644 --- a/crates/assists/src/handlers/invert_if.rs +++ b/crates/assists/src/handlers/invert_if.rs | |||
@@ -49,13 +49,14 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
49 | ast::ElseBranch::IfExpr(_) => return None, | 49 | ast::ElseBranch::IfExpr(_) => return None, |
50 | }; | 50 | }; |
51 | 51 | ||
52 | let cond_range = cond.syntax().text_range(); | ||
53 | let flip_cond = invert_boolean_expression(cond); | ||
54 | let else_node = else_block.syntax(); | ||
55 | let else_range = else_node.text_range(); | ||
56 | let then_range = then_node.text_range(); | ||
57 | acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { | 52 | acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { |
58 | edit.replace(cond_range, flip_cond.syntax().text()); | 53 | let flip_cond = invert_boolean_expression(cond.clone()); |
54 | edit.replace_ast(cond, flip_cond); | ||
55 | |||
56 | let else_node = else_block.syntax(); | ||
57 | let else_range = else_node.text_range(); | ||
58 | let then_range = then_node.text_range(); | ||
59 | |||
59 | edit.replace(else_range, then_node.text()); | 60 | edit.replace(else_range, then_node.text()); |
60 | edit.replace(then_range, else_node.text()); | 61 | edit.replace(then_range, else_node.text()); |
61 | }) | 62 | }) |
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs index 0bd679260..fe33cee53 100644 --- a/crates/assists/src/handlers/merge_imports.rs +++ b/crates/assists/src/handlers/merge_imports.rs | |||
@@ -95,7 +95,7 @@ use std::fmt::Debug; | |||
95 | use std::fmt<|>::Display; | 95 | use std::fmt<|>::Display; |
96 | ", | 96 | ", |
97 | r" | 97 | r" |
98 | use std::fmt::{Display, Debug}; | 98 | use std::fmt::{Debug, Display}; |
99 | ", | 99 | ", |
100 | ); | 100 | ); |
101 | } | 101 | } |
@@ -122,7 +122,7 @@ use std::fmt::{self, Display}; | |||
122 | use std::{fmt, <|>fmt::Display}; | 122 | use std::{fmt, <|>fmt::Display}; |
123 | ", | 123 | ", |
124 | r" | 124 | r" |
125 | use std::{fmt::{Display, self}}; | 125 | use std::{fmt::{self, Display}}; |
126 | ", | 126 | ", |
127 | ); | 127 | ); |
128 | } | 128 | } |
@@ -210,13 +210,17 @@ use std::{fmt<|>::Debug, fmt::Display}; | |||
210 | use std::{fmt::{Debug, Display}}; | 210 | use std::{fmt::{Debug, Display}}; |
211 | ", | 211 | ", |
212 | ); | 212 | ); |
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn test_merge_nested2() { | ||
213 | check_assist( | 217 | check_assist( |
214 | merge_imports, | 218 | merge_imports, |
215 | r" | 219 | r" |
216 | use std::{fmt::Debug, fmt<|>::Display}; | 220 | use std::{fmt::Debug, fmt<|>::Display}; |
217 | ", | 221 | ", |
218 | r" | 222 | r" |
219 | use std::{fmt::{Display, Debug}}; | 223 | use std::{fmt::{Debug, Display}}; |
220 | ", | 224 | ", |
221 | ); | 225 | ); |
222 | } | 226 | } |
@@ -310,9 +314,7 @@ use foo::<|>{ | |||
310 | }; | 314 | }; |
311 | ", | 315 | ", |
312 | r" | 316 | r" |
313 | use foo::{ | 317 | use foo::{FooBar, bar::baz}; |
314 | FooBar, | ||
315 | bar::baz}; | ||
316 | ", | 318 | ", |
317 | ) | 319 | ) |
318 | } | 320 | } |
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs index 27b49455c..74afc123b 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -298,7 +298,7 @@ impl std::fmt<|> for Foo { | |||
298 | } | 298 | } |
299 | ", | 299 | ", |
300 | r" | 300 | r" |
301 | use std::fmt::{Debug, self}; | 301 | use std::fmt::{self, Debug}; |
302 | 302 | ||
303 | impl fmt for Foo { | 303 | impl fmt for Foo { |
304 | } | 304 | } |
@@ -316,9 +316,8 @@ use std::fmt::{Debug, nested::{Display}}; | |||
316 | impl std::fmt::nested<|> for Foo { | 316 | impl std::fmt::nested<|> for Foo { |
317 | } | 317 | } |
318 | ", | 318 | ", |
319 | // FIXME(veykril): should be nested::{self, Display} here | ||
320 | r" | 319 | r" |
321 | use std::fmt::{Debug, nested::{Display}, nested}; | 320 | use std::fmt::{Debug, nested::{self, Display}}; |
322 | 321 | ||
323 | impl nested for Foo { | 322 | impl nested for Foo { |
324 | } | 323 | } |
@@ -336,9 +335,8 @@ use std::fmt::{Debug, nested::{self, Display}}; | |||
336 | impl std::fmt::nested<|> for Foo { | 335 | impl std::fmt::nested<|> for Foo { |
337 | } | 336 | } |
338 | ", | 337 | ", |
339 | // FIXME(veykril): nested is duplicated now | ||
340 | r" | 338 | r" |
341 | use std::fmt::{Debug, nested::{self, Display}, nested}; | 339 | use std::fmt::{Debug, nested::{self, Display}}; |
342 | 340 | ||
343 | impl nested for Foo { | 341 | impl nested for Foo { |
344 | } | 342 | } |
@@ -357,7 +355,7 @@ impl std::fmt::nested::Debug<|> for Foo { | |||
357 | } | 355 | } |
358 | ", | 356 | ", |
359 | r" | 357 | r" |
360 | use std::fmt::{Debug, nested::{Display}, nested::Debug}; | 358 | use std::fmt::{Debug, nested::{Debug, Display}}; |
361 | 359 | ||
362 | impl Debug for Foo { | 360 | impl Debug for Foo { |
363 | } | 361 | } |
@@ -395,7 +393,7 @@ impl std::fmt::Display<|> for Foo { | |||
395 | } | 393 | } |
396 | ", | 394 | ", |
397 | r" | 395 | r" |
398 | use std::fmt::{nested::Debug, Display}; | 396 | use std::fmt::{Display, nested::Debug}; |
399 | 397 | ||
400 | impl Display for Foo { | 398 | impl Display for Foo { |
401 | } | 399 | } |
@@ -415,12 +413,8 @@ use crate::{ | |||
415 | 413 | ||
416 | fn foo() { crate::ty::lower<|>::trait_env() } | 414 | fn foo() { crate::ty::lower<|>::trait_env() } |
417 | ", | 415 | ", |
418 | // FIXME(veykril): formatting broke here | ||
419 | r" | 416 | r" |
420 | use crate::{ | 417 | use crate::{AssocItem, ty::{Substs, Ty, lower}}; |
421 | ty::{Substs, Ty}, | ||
422 | AssocItem, | ||
423 | ty::lower}; | ||
424 | 418 | ||
425 | fn foo() { lower::trait_env() } | 419 | fn foo() { lower::trait_env() } |
426 | ", | 420 | ", |
@@ -619,7 +613,7 @@ fn main() { | |||
619 | } | 613 | } |
620 | ", | 614 | ", |
621 | r" | 615 | r" |
622 | use std::fmt::{Display, self}; | 616 | use std::fmt::{self, Display}; |
623 | 617 | ||
624 | fn main() { | 618 | fn main() { |
625 | fmt; | 619 | fmt; |
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 6d110aaaf..5719b06af 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | //! Handle syntactic aspects of inserting a new `use`. | 1 | //! Handle syntactic aspects of inserting a new `use`. |
2 | use std::iter::{self, successors}; | 2 | use std::{ |
3 | cmp::Ordering, | ||
4 | iter::{self, successors}, | ||
5 | }; | ||
3 | 6 | ||
4 | use algo::skip_trivia_token; | ||
5 | use ast::{ | 7 | use ast::{ |
6 | edit::{AstNodeEdit, IndentLevel}, | 8 | edit::{AstNodeEdit, IndentLevel}, |
7 | PathSegmentKind, VisibilityOwner, | 9 | PathSegmentKind, VisibilityOwner, |
@@ -9,9 +11,8 @@ use ast::{ | |||
9 | use syntax::{ | 11 | use syntax::{ |
10 | algo, | 12 | algo, |
11 | ast::{self, make, AstNode}, | 13 | ast::{self, make, AstNode}, |
12 | Direction, InsertPosition, SyntaxElement, SyntaxNode, T, | 14 | InsertPosition, SyntaxElement, SyntaxNode, |
13 | }; | 15 | }; |
14 | use test_utils::mark; | ||
15 | 16 | ||
16 | #[derive(Debug)] | 17 | #[derive(Debug)] |
17 | pub enum ImportScope { | 18 | pub enum ImportScope { |
@@ -119,7 +120,6 @@ pub(crate) fn insert_use( | |||
119 | } | 120 | } |
120 | 121 | ||
121 | if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { | 122 | if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { |
122 | // FIXME: this alone doesnt properly re-align all cases | ||
123 | buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()); | 123 | buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()); |
124 | } | 124 | } |
125 | buf.push(use_item.syntax().clone().into()); | 125 | buf.push(use_item.syntax().clone().into()); |
@@ -149,66 +149,123 @@ fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) - | |||
149 | } | 149 | } |
150 | 150 | ||
151 | pub(crate) fn try_merge_imports( | 151 | pub(crate) fn try_merge_imports( |
152 | old: &ast::Use, | 152 | lhs: &ast::Use, |
153 | new: &ast::Use, | 153 | rhs: &ast::Use, |
154 | merge_behaviour: MergeBehaviour, | 154 | merge_behaviour: MergeBehaviour, |
155 | ) -> Option<ast::Use> { | 155 | ) -> Option<ast::Use> { |
156 | // don't merge imports with different visibilities | 156 | // don't merge imports with different visibilities |
157 | if !eq_visibility(old.visibility(), new.visibility()) { | 157 | if !eq_visibility(lhs.visibility(), rhs.visibility()) { |
158 | return None; | 158 | return None; |
159 | } | 159 | } |
160 | let old_tree = old.use_tree()?; | 160 | let lhs_tree = lhs.use_tree()?; |
161 | let new_tree = new.use_tree()?; | 161 | let rhs_tree = rhs.use_tree()?; |
162 | let merged = try_merge_trees(&old_tree, &new_tree, merge_behaviour)?; | 162 | let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?; |
163 | Some(old.with_use_tree(merged)) | 163 | Some(lhs.with_use_tree(merged)) |
164 | } | ||
165 | |||
166 | /// Simple function that checks if a UseTreeList is deeper than one level | ||
167 | fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool { | ||
168 | tl.use_trees().any(|use_tree| { | ||
169 | use_tree.use_tree_list().is_some() || use_tree.path().and_then(|p| p.qualifier()).is_some() | ||
170 | }) | ||
171 | } | 164 | } |
172 | 165 | ||
173 | // FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion | ||
174 | pub(crate) fn try_merge_trees( | 166 | pub(crate) fn try_merge_trees( |
175 | old: &ast::UseTree, | 167 | lhs: &ast::UseTree, |
176 | new: &ast::UseTree, | 168 | rhs: &ast::UseTree, |
177 | merge_behaviour: MergeBehaviour, | 169 | merge: MergeBehaviour, |
178 | ) -> Option<ast::UseTree> { | 170 | ) -> Option<ast::UseTree> { |
179 | let lhs_path = old.path()?; | 171 | let lhs_path = lhs.path()?; |
180 | let rhs_path = new.path()?; | 172 | let rhs_path = rhs.path()?; |
181 | 173 | ||
182 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | 174 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; |
183 | let lhs = old.split_prefix(&lhs_prefix); | 175 | let lhs = lhs.split_prefix(&lhs_prefix); |
184 | let rhs = new.split_prefix(&rhs_prefix); | 176 | let rhs = rhs.split_prefix(&rhs_prefix); |
185 | let lhs_tl = lhs.use_tree_list()?; | 177 | recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged) |
186 | let rhs_tl = rhs.use_tree_list()?; | 178 | } |
187 | |||
188 | // if we are only allowed to merge the last level check if the split off paths are only one level deep | ||
189 | if merge_behaviour == MergeBehaviour::Last | ||
190 | && (use_tree_list_is_nested(&lhs_tl) || use_tree_list_is_nested(&rhs_tl)) | ||
191 | { | ||
192 | mark::hit!(test_last_merge_too_long); | ||
193 | return None; | ||
194 | } | ||
195 | 179 | ||
196 | let should_insert_comma = lhs_tl | 180 | /// Recursively "zips" together lhs and rhs. |
197 | .r_curly_token() | 181 | fn recursive_merge( |
198 | .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev)) | 182 | lhs: &ast::UseTree, |
199 | .map(|it| it.kind()) | 183 | rhs: &ast::UseTree, |
200 | != Some(T![,]); | 184 | merge: MergeBehaviour, |
201 | let mut to_insert: Vec<SyntaxElement> = Vec::new(); | 185 | ) -> Option<(ast::UseTree, bool)> { |
202 | if should_insert_comma { | 186 | let mut use_trees = lhs |
203 | to_insert.push(make::token(T![,]).into()); | 187 | .use_tree_list() |
204 | to_insert.push(make::tokens::single_space().into()); | 188 | .into_iter() |
205 | } | 189 | .flat_map(|list| list.use_trees()) |
206 | to_insert.extend( | 190 | // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this |
207 | rhs_tl.syntax().children_with_tokens().filter(|it| !matches!(it.kind(), T!['{'] | T!['}'])), | 191 | // so early exit the iterator by using Option's Intoiterator impl |
208 | ); | 192 | .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() { |
209 | let pos = InsertPosition::Before(lhs_tl.r_curly_token()?.into()); | 193 | true => None, |
210 | let use_tree_list = lhs_tl.insert_children(pos, to_insert); | 194 | false => Some(tree), |
211 | Some(lhs.with_use_tree_list(use_tree_list)) | 195 | }) |
196 | .collect::<Option<Vec<_>>>()?; | ||
197 | use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path())); | ||
198 | for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) { | ||
199 | let rhs_path = rhs_t.path(); | ||
200 | match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) { | ||
201 | Ok(idx) => { | ||
202 | let lhs_t = &mut use_trees[idx]; | ||
203 | let lhs_path = lhs_t.path()?; | ||
204 | let rhs_path = rhs_path?; | ||
205 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | ||
206 | if lhs_prefix == lhs_path && rhs_prefix == rhs_path { | ||
207 | let tree_is_self = |tree: ast::UseTree| { | ||
208 | tree.path().as_ref().map(path_is_self).unwrap_or(false) | ||
209 | }; | ||
210 | // check if only one of the two trees has a tree list, and whether that then contains `self` or not. | ||
211 | // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self` | ||
212 | let tree_contains_self = |tree: &ast::UseTree| { | ||
213 | tree.use_tree_list() | ||
214 | .map(|tree_list| tree_list.use_trees().any(tree_is_self)) | ||
215 | .unwrap_or(false) | ||
216 | }; | ||
217 | match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) { | ||
218 | (true, false) => continue, | ||
219 | (false, true) => { | ||
220 | *lhs_t = rhs_t; | ||
221 | continue; | ||
222 | } | ||
223 | _ => (), | ||
224 | } | ||
225 | |||
226 | // glob imports arent part of the use-tree lists so we need to special handle them here as well | ||
227 | // this special handling is only required for when we merge a module import into a glob import of said module | ||
228 | // see the `merge_self_glob` or `merge_mod_into_glob` tests | ||
229 | if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() { | ||
230 | *lhs_t = make::use_tree( | ||
231 | make::path_unqualified(make::path_segment_self()), | ||
232 | None, | ||
233 | None, | ||
234 | false, | ||
235 | ); | ||
236 | use_trees.insert(idx, make::glob_use_tree()); | ||
237 | continue; | ||
238 | } | ||
239 | } | ||
240 | let lhs = lhs_t.split_prefix(&lhs_prefix); | ||
241 | let rhs = rhs_t.split_prefix(&rhs_prefix); | ||
242 | let this_has_children = use_trees.len() > 0; | ||
243 | match recursive_merge(&lhs, &rhs, merge) { | ||
244 | Some((_, has_multiple_children)) | ||
245 | if merge == MergeBehaviour::Last | ||
246 | && this_has_children | ||
247 | && has_multiple_children => | ||
248 | { | ||
249 | return None | ||
250 | } | ||
251 | Some((use_tree, _)) => use_trees[idx] = use_tree, | ||
252 | None => use_trees.insert(idx, rhs_t), | ||
253 | } | ||
254 | } | ||
255 | Err(_) | ||
256 | if merge == MergeBehaviour::Last | ||
257 | && use_trees.len() > 0 | ||
258 | && rhs_t.use_tree_list().is_some() => | ||
259 | { | ||
260 | return None | ||
261 | } | ||
262 | Err(idx) => { | ||
263 | use_trees.insert(idx, rhs_t); | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | let has_multiple_children = use_trees.len() > 1; | ||
268 | Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children)) | ||
212 | } | 269 | } |
213 | 270 | ||
214 | /// Traverses both paths until they differ, returning the common prefix of both. | 271 | /// Traverses both paths until they differ, returning the common prefix of both. |
@@ -219,7 +276,7 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa | |||
219 | loop { | 276 | loop { |
220 | match (lhs_curr.segment(), rhs_curr.segment()) { | 277 | match (lhs_curr.segment(), rhs_curr.segment()) { |
221 | (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), | 278 | (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), |
222 | _ => break, | 279 | _ => break res, |
223 | } | 280 | } |
224 | res = Some((lhs_curr.clone(), rhs_curr.clone())); | 281 | res = Some((lhs_curr.clone(), rhs_curr.clone())); |
225 | 282 | ||
@@ -228,11 +285,62 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa | |||
228 | lhs_curr = lhs; | 285 | lhs_curr = lhs; |
229 | rhs_curr = rhs; | 286 | rhs_curr = rhs; |
230 | } | 287 | } |
231 | _ => break, | 288 | _ => break res, |
289 | } | ||
290 | } | ||
291 | } | ||
292 | |||
293 | fn path_is_self(path: &ast::Path) -> bool { | ||
294 | path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none() | ||
295 | } | ||
296 | |||
297 | #[inline] | ||
298 | fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> { | ||
299 | first_path(path).segment() | ||
300 | } | ||
301 | |||
302 | fn first_path(path: &ast::Path) -> ast::Path { | ||
303 | successors(Some(path.clone()), ast::Path::qualifier).last().unwrap() | ||
304 | } | ||
305 | |||
306 | fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { | ||
307 | // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone | ||
308 | successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) | ||
309 | } | ||
310 | |||
311 | /// Orders paths in the following way: | ||
312 | /// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers | ||
313 | // FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has | ||
314 | // which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports. | ||
315 | // Example foo::{self, foo, baz, Baz, Qux, *, {Bar}} | ||
316 | fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering { | ||
317 | match (path_is_self(a), path_is_self(b)) { | ||
318 | (true, true) => Ordering::Equal, | ||
319 | (true, false) => Ordering::Less, | ||
320 | (false, true) => Ordering::Greater, | ||
321 | (false, false) => { | ||
322 | let a = segment_iter(a); | ||
323 | let b = segment_iter(b); | ||
324 | // cmp_by would be useful for us here but that is currently unstable | ||
325 | // cmp doesnt work due the lifetimes on text's return type | ||
326 | a.zip(b) | ||
327 | .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref())) | ||
328 | .find_map(|(a, b)| match a.text().cmp(b.text()) { | ||
329 | ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord), | ||
330 | Ordering::Equal => None, | ||
331 | }) | ||
332 | .unwrap_or(Ordering::Equal) | ||
232 | } | 333 | } |
233 | } | 334 | } |
335 | } | ||
234 | 336 | ||
235 | res | 337 | fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering { |
338 | match (a, b) { | ||
339 | (None, None) => Ordering::Equal, | ||
340 | (None, Some(_)) => Ordering::Less, | ||
341 | (Some(_), None) => Ordering::Greater, | ||
342 | (Some(a), Some(b)) => path_cmp(&a, &b), | ||
343 | } | ||
236 | } | 344 | } |
237 | 345 | ||
238 | /// What type of merges are allowed. | 346 | /// What type of merges are allowed. |
@@ -279,19 +387,6 @@ impl ImportGroup { | |||
279 | } | 387 | } |
280 | } | 388 | } |
281 | 389 | ||
282 | fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> { | ||
283 | first_path(path).segment() | ||
284 | } | ||
285 | |||
286 | fn first_path(path: &ast::Path) -> ast::Path { | ||
287 | successors(Some(path.clone()), ast::Path::qualifier).last().unwrap() | ||
288 | } | ||
289 | |||
290 | fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { | ||
291 | // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone | ||
292 | successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) | ||
293 | } | ||
294 | |||
295 | #[derive(PartialEq, Eq)] | 390 | #[derive(PartialEq, Eq)] |
296 | enum AddBlankLine { | 391 | enum AddBlankLine { |
297 | Before, | 392 | Before, |
@@ -594,7 +689,7 @@ use std::io;", | |||
594 | check_full( | 689 | check_full( |
595 | "std::foo::bar::Baz", | 690 | "std::foo::bar::Baz", |
596 | r"use std::foo::bar::Qux;", | 691 | r"use std::foo::bar::Qux;", |
597 | r"use std::foo::bar::{Qux, Baz};", | 692 | r"use std::foo::bar::{Baz, Qux};", |
598 | ) | 693 | ) |
599 | } | 694 | } |
600 | 695 | ||
@@ -603,7 +698,7 @@ use std::io;", | |||
603 | check_last( | 698 | check_last( |
604 | "std::foo::bar::Baz", | 699 | "std::foo::bar::Baz", |
605 | r"use std::foo::bar::Qux;", | 700 | r"use std::foo::bar::Qux;", |
606 | r"use std::foo::bar::{Qux, Baz};", | 701 | r"use std::foo::bar::{Baz, Qux};", |
607 | ) | 702 | ) |
608 | } | 703 | } |
609 | 704 | ||
@@ -612,7 +707,7 @@ use std::io;", | |||
612 | check_full( | 707 | check_full( |
613 | "std::foo::bar::Baz", | 708 | "std::foo::bar::Baz", |
614 | r"use std::foo::bar::{Qux, Quux};", | 709 | r"use std::foo::bar::{Qux, Quux};", |
615 | r"use std::foo::bar::{Qux, Quux, Baz};", | 710 | r"use std::foo::bar::{Baz, Quux, Qux};", |
616 | ) | 711 | ) |
617 | } | 712 | } |
618 | 713 | ||
@@ -621,7 +716,7 @@ use std::io;", | |||
621 | check_last( | 716 | check_last( |
622 | "std::foo::bar::Baz", | 717 | "std::foo::bar::Baz", |
623 | r"use std::foo::bar::{Qux, Quux};", | 718 | r"use std::foo::bar::{Qux, Quux};", |
624 | r"use std::foo::bar::{Qux, Quux, Baz};", | 719 | r"use std::foo::bar::{Baz, Quux, Qux};", |
625 | ) | 720 | ) |
626 | } | 721 | } |
627 | 722 | ||
@@ -630,7 +725,7 @@ use std::io;", | |||
630 | check_full( | 725 | check_full( |
631 | "std::foo::bar::Baz", | 726 | "std::foo::bar::Baz", |
632 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | 727 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", |
633 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};", | 728 | r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};", |
634 | ) | 729 | ) |
635 | } | 730 | } |
636 | 731 | ||
@@ -645,6 +740,15 @@ use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | |||
645 | } | 740 | } |
646 | 741 | ||
647 | #[test] | 742 | #[test] |
743 | fn merge_groups_full_nested_deep() { | ||
744 | check_full( | ||
745 | "std::foo::bar::quux::Baz", | ||
746 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | ||
747 | r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", | ||
748 | ) | ||
749 | } | ||
750 | |||
751 | #[test] | ||
648 | fn merge_groups_skip_pub() { | 752 | fn merge_groups_skip_pub() { |
649 | check_full( | 753 | check_full( |
650 | "std::io", | 754 | "std::io", |
@@ -670,34 +774,53 @@ use std::io;", | |||
670 | check_last( | 774 | check_last( |
671 | "std::fmt::Result", | 775 | "std::fmt::Result", |
672 | r"use std::{fmt, io};", | 776 | r"use std::{fmt, io};", |
673 | r"use std::{self, fmt::Result}; | 777 | r"use std::fmt::{self, Result}; |
674 | use std::io;", | 778 | use std::io;", |
675 | ) | 779 | ) |
676 | } | 780 | } |
677 | 781 | ||
678 | #[test] | 782 | #[test] |
783 | fn merge_into_module_import() { | ||
784 | check_full( | ||
785 | "std::fmt::Result", | ||
786 | r"use std::{fmt, io};", | ||
787 | r"use std::{fmt::{self, Result}, io};", | ||
788 | ) | ||
789 | } | ||
790 | |||
791 | #[test] | ||
679 | fn merge_groups_self() { | 792 | fn merge_groups_self() { |
680 | check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") | 793 | check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") |
681 | } | 794 | } |
682 | 795 | ||
683 | #[test] | 796 | #[test] |
684 | fn merge_self_glob() { | 797 | fn merge_mod_into_glob() { |
685 | check_full( | 798 | check_full( |
686 | "token::TokenKind", | 799 | "token::TokenKind", |
687 | r"use token::TokenKind::*;", | 800 | r"use token::TokenKind::*;", |
688 | r"use token::TokenKind::{self::*, self};", | 801 | r"use token::TokenKind::{*, self};", |
802 | ) | ||
803 | // FIXME: have it emit `use token::TokenKind::{self, *}`? | ||
804 | } | ||
805 | |||
806 | #[test] | ||
807 | fn merge_self_glob() { | ||
808 | check_full("self", r"use self::*;", r"use self::{*, self};") | ||
809 | // FIXME: have it emit `use {self, *}`? | ||
810 | } | ||
811 | |||
812 | #[test] | ||
813 | fn merge_glob_nested() { | ||
814 | check_full( | ||
815 | "foo::bar::quux::Fez", | ||
816 | r"use foo::bar::{Baz, quux::*};", | ||
817 | r"use foo::bar::{Baz, quux::{self::*, Fez}};", | ||
689 | ) | 818 | ) |
690 | } | 819 | } |
691 | 820 | ||
692 | #[test] | 821 | #[test] |
693 | fn merge_last_too_long() { | 822 | fn merge_last_too_long() { |
694 | mark::check!(test_last_merge_too_long); | 823 | check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};"); |
695 | check_last( | ||
696 | "foo::bar", | ||
697 | r"use foo::bar::baz::Qux;", | ||
698 | r"use foo::bar; | ||
699 | use foo::bar::baz::Qux;", | ||
700 | ); | ||
701 | } | 824 | } |
702 | 825 | ||
703 | #[test] | 826 | #[test] |
@@ -710,6 +833,42 @@ use foo::bar::baz::Qux;", | |||
710 | ); | 833 | ); |
711 | } | 834 | } |
712 | 835 | ||
836 | #[test] | ||
837 | fn merge_last_fail() { | ||
838 | check_merge_only_fail( | ||
839 | r"use foo::bar::{baz::{Qux, Fez}};", | ||
840 | r"use foo::bar::{baaz::{Quux, Feez}};", | ||
841 | MergeBehaviour::Last, | ||
842 | ); | ||
843 | } | ||
844 | |||
845 | #[test] | ||
846 | fn merge_last_fail1() { | ||
847 | check_merge_only_fail( | ||
848 | r"use foo::bar::{baz::{Qux, Fez}};", | ||
849 | r"use foo::bar::baaz::{Quux, Feez};", | ||
850 | MergeBehaviour::Last, | ||
851 | ); | ||
852 | } | ||
853 | |||
854 | #[test] | ||
855 | fn merge_last_fail2() { | ||
856 | check_merge_only_fail( | ||
857 | r"use foo::bar::baz::{Qux, Fez};", | ||
858 | r"use foo::bar::{baaz::{Quux, Feez}};", | ||
859 | MergeBehaviour::Last, | ||
860 | ); | ||
861 | } | ||
862 | |||
863 | #[test] | ||
864 | fn merge_last_fail3() { | ||
865 | check_merge_only_fail( | ||
866 | r"use foo::bar::baz::{Qux, Fez};", | ||
867 | r"use foo::bar::baaz::{Quux, Feez};", | ||
868 | MergeBehaviour::Last, | ||
869 | ); | ||
870 | } | ||
871 | |||
713 | fn check( | 872 | fn check( |
714 | path: &str, | 873 | path: &str, |
715 | ra_fixture_before: &str, | 874 | ra_fixture_before: &str, |
@@ -742,4 +901,23 @@ use foo::bar::baz::Qux;", | |||
742 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 901 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
743 | check(path, ra_fixture_before, ra_fixture_after, None) | 902 | check(path, ra_fixture_before, ra_fixture_after, None) |
744 | } | 903 | } |
904 | |||
905 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) { | ||
906 | let use0 = ast::SourceFile::parse(ra_fixture0) | ||
907 | .tree() | ||
908 | .syntax() | ||
909 | .descendants() | ||
910 | .find_map(ast::Use::cast) | ||
911 | .unwrap(); | ||
912 | |||
913 | let use1 = ast::SourceFile::parse(ra_fixture1) | ||
914 | .tree() | ||
915 | .syntax() | ||
916 | .descendants() | ||
917 | .find_map(ast::Use::cast) | ||
918 | .unwrap(); | ||
919 | |||
920 | let result = try_merge_imports(&use0, &use1, mb); | ||
921 | assert_eq!(result.map(|u| u.to_string()), None); | ||
922 | } | ||
745 | } | 923 | } |