aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-09-12 18:18:14 +0100
committerLukas Wirth <[email protected]>2020-09-12 18:19:19 +0100
commita898752881779a328462ad9f2db291073f2f134f (patch)
tree4b6894416bf4f2a875b5bd4a3bc20510a00821d5 /crates
parentc8623461a57e7882ac47b5da13a1a03efa58f603 (diff)
Reimplement import merging by making it recursive properly nesting all levels
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/merge_imports.rs14
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs20
-rw-r--r--crates/assists/src/utils/insert_use.rs312
-rw-r--r--crates/syntax/src/ast/edit.rs1
4 files changed, 264 insertions, 83 deletions
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;
95use std::fmt<|>::Display; 95use std::fmt<|>::Display;
96", 96",
97 r" 97 r"
98use std::fmt::{Display, Debug}; 98use std::fmt::{Debug, Display};
99", 99",
100 ); 100 );
101 } 101 }
@@ -122,7 +122,7 @@ use std::fmt::{self, Display};
122use std::{fmt, <|>fmt::Display}; 122use std::{fmt, <|>fmt::Display};
123", 123",
124 r" 124 r"
125use std::{fmt::{Display, self}}; 125use std::{fmt::{self, Display}};
126", 126",
127 ); 127 );
128 } 128 }
@@ -210,13 +210,17 @@ use std::{fmt<|>::Debug, fmt::Display};
210use std::{fmt::{Debug, Display}}; 210use 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"
216use std::{fmt::Debug, fmt<|>::Display}; 220use std::{fmt::Debug, fmt<|>::Display};
217", 221",
218 r" 222 r"
219use std::{fmt::{Display, Debug}}; 223use std::{fmt::{Debug, Display}};
220", 224",
221 ); 225 );
222 } 226 }
@@ -310,9 +314,7 @@ use foo::<|>{
310}; 314};
311", 315",
312 r" 316 r"
313use foo::{ 317use foo::{FooBar, bar::baz};
314 FooBar,
315bar::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 85c70d16b..093c3b101 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -312,7 +312,7 @@ impl std::fmt<|> for Foo {
312} 312}
313 ", 313 ",
314 r" 314 r"
315use std::fmt::{Debug, self}; 315use std::fmt::{self, Debug};
316 316
317impl fmt for Foo { 317impl fmt for Foo {
318} 318}
@@ -330,9 +330,8 @@ use std::fmt::{Debug, nested::{Display}};
330impl std::fmt::nested<|> for Foo { 330impl std::fmt::nested<|> for Foo {
331} 331}
332", 332",
333 // FIXME(veykril): should be nested::{self, Display} here
334 r" 333 r"
335use std::fmt::{Debug, nested::{Display}, nested}; 334use std::fmt::{Debug, nested::{self, Display}};
336 335
337impl nested for Foo { 336impl nested for Foo {
338} 337}
@@ -350,9 +349,8 @@ use std::fmt::{Debug, nested::{self, Display}};
350impl std::fmt::nested<|> for Foo { 349impl std::fmt::nested<|> for Foo {
351} 350}
352", 351",
353 // FIXME(veykril): nested is duplicated now
354 r" 352 r"
355use std::fmt::{Debug, nested::{self, Display}, nested}; 353use std::fmt::{Debug, nested::{self, Display}};
356 354
357impl nested for Foo { 355impl nested for Foo {
358} 356}
@@ -371,7 +369,7 @@ impl std::fmt::nested::Debug<|> for Foo {
371} 369}
372", 370",
373 r" 371 r"
374use std::fmt::{Debug, nested::{Display}, nested::Debug}; 372use std::fmt::{Debug, nested::{Debug, Display}};
375 373
376impl Debug for Foo { 374impl Debug for Foo {
377} 375}
@@ -409,7 +407,7 @@ impl std::fmt::Display<|> for Foo {
409} 407}
410", 408",
411 r" 409 r"
412use std::fmt::{nested::Debug, Display}; 410use std::fmt::{Display, nested::Debug};
413 411
414impl Display for Foo { 412impl Display for Foo {
415} 413}
@@ -429,12 +427,8 @@ use crate::{
429 427
430fn foo() { crate::ty::lower<|>::trait_env() } 428fn foo() { crate::ty::lower<|>::trait_env() }
431", 429",
432 // FIXME(veykril): formatting broke here
433 r" 430 r"
434use crate::{ 431use crate::{AssocItem, ty::{Substs, Ty, lower}};
435 ty::{Substs, Ty},
436 AssocItem,
437ty::lower};
438 432
439fn foo() { lower::trait_env() } 433fn foo() { lower::trait_env() }
440", 434",
@@ -633,7 +627,7 @@ fn main() {
633} 627}
634 ", 628 ",
635 r" 629 r"
636use std::fmt::{Display, self}; 630use std::fmt::{self, Display};
637 631
638fn main() { 632fn main() {
639 fmt; 633 fmt;
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 98553b2e0..4f5fd317a 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`.
2use std::iter::{self, successors}; 2use std::{
3 cmp::Ordering,
4 iter::{self, successors},
5};
3 6
4use algo::skip_trivia_token;
5use ast::{ 7use ast::{
6 edit::{AstNodeEdit, IndentLevel}, 8 edit::{AstNodeEdit, IndentLevel},
7 PathSegmentKind, VisibilityOwner, 9 PathSegmentKind, VisibilityOwner,
@@ -9,9 +11,8 @@ use ast::{
9use syntax::{ 11use 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};
14use test_utils::mark;
15 16
16#[derive(Debug)] 17#[derive(Debug)]
17pub enum ImportScope { 18pub enum ImportScope {
@@ -163,18 +164,48 @@ pub(crate) fn try_merge_imports(
163 Some(old.with_use_tree(merged)) 164 Some(old.with_use_tree(merged))
164} 165}
165 166
166/// Simple function that checks if a UseTreeList is deeper than one level 167/// Orders paths in the following way:
167fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool { 168/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
168 tl.use_trees().any(|use_tree| { 169/// FIXME: rustfmt sort lowercase idents before uppercase
169 use_tree.use_tree_list().is_some() || use_tree.path().and_then(|p| p.qualifier()).is_some() 170fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
170 }) 171 let a = segment_iter(a);
172 let b = segment_iter(b);
173 let mut a_clone = a.clone();
174 let mut b_clone = b.clone();
175 match (
176 a_clone.next().and_then(|ps| ps.self_token()).is_some() && a_clone.next().is_none(),
177 b_clone.next().and_then(|ps| ps.self_token()).is_some() && b_clone.next().is_none(),
178 ) {
179 (true, true) => Ordering::Equal,
180 (true, false) => Ordering::Less,
181 (false, true) => Ordering::Greater,
182 (false, false) => {
183 // cmp_by would be useful for us here but that is currently unstable
184 // cmp doesnt work due the lifetimes on text's return type
185 a.zip(b)
186 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
187 .find_map(|(a, b)| match a.text().cmp(b.text()) {
188 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
189 Ordering::Equal => None,
190 })
191 .unwrap_or(Ordering::Equal)
192 }
193 }
194}
195
196fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
197 match (a, b) {
198 (None, None) => Ordering::Equal,
199 (None, Some(_)) => Ordering::Less,
200 (Some(_), None) => Ordering::Greater,
201 (Some(a), Some(b)) => path_cmp(&a, &b),
202 }
171} 203}
172 204
173// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion
174pub(crate) fn try_merge_trees( 205pub(crate) fn try_merge_trees(
175 old: &ast::UseTree, 206 old: &ast::UseTree,
176 new: &ast::UseTree, 207 new: &ast::UseTree,
177 merge_behaviour: MergeBehaviour, 208 merge: MergeBehaviour,
178) -> Option<ast::UseTree> { 209) -> Option<ast::UseTree> {
179 let lhs_path = old.path()?; 210 let lhs_path = old.path()?;
180 let rhs_path = new.path()?; 211 let rhs_path = new.path()?;
@@ -182,33 +213,89 @@ pub(crate) fn try_merge_trees(
182 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; 213 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
183 let lhs = old.split_prefix(&lhs_prefix); 214 let lhs = old.split_prefix(&lhs_prefix);
184 let rhs = new.split_prefix(&rhs_prefix); 215 let rhs = new.split_prefix(&rhs_prefix);
185 let lhs_tl = lhs.use_tree_list()?; 216 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged)
186 let rhs_tl = rhs.use_tree_list()?; 217}
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 218
196 let should_insert_comma = lhs_tl 219/// Returns the merged tree and the number of children it has, which is required to check if the tree is allowed to be used for MergeBehaviour::Last
197 .r_curly_token() 220fn recursive_merge(
198 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev)) 221 lhs: &ast::UseTree,
199 .map(|it| it.kind()) 222 rhs: &ast::UseTree,
200 != Some(T![,]); 223 merge: MergeBehaviour,
201 let mut to_insert: Vec<SyntaxElement> = Vec::new(); 224) -> Option<(ast::UseTree, usize)> {
202 if should_insert_comma { 225 let mut use_trees = lhs
203 to_insert.push(make::token(T![,]).into()); 226 .use_tree_list()
204 to_insert.push(make::tokens::single_space().into()); 227 .into_iter()
205 } 228 .flat_map(|list| list.use_trees())
206 to_insert.extend( 229 // check if any of the use trees are nested, if they are and the behaviour is last only we are not allowed to merge this
207 rhs_tl.syntax().children_with_tokens().filter(|it| !matches!(it.kind(), T!['{'] | T!['}'])), 230 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() {
208 ); 231 true => None,
209 let pos = InsertPosition::Before(lhs_tl.r_curly_token()?.into()); 232 false => Some(tree),
210 let use_tree_list = lhs_tl.insert_children(pos, to_insert); 233 })
211 Some(lhs.with_use_tree_list(use_tree_list)) 234 .collect::<Option<Vec<_>>>()?;
235 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
236 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
237 let rhs_path = rhs_t.path();
238 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
239 Ok(idx) => {
240 let lhs_path = use_trees[idx].path()?;
241 let rhs_path = rhs_path?;
242 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
243 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
244 let tree_is_self =
245 |tree: ast::UseTree| tree.path().map(path_is_self).unwrap_or(false);
246 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
247 // 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`
248 if use_trees[idx]
249 .use_tree_list()
250 .xor(rhs_t.use_tree_list())
251 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
252 .unwrap_or(false)
253 {
254 continue;
255 }
256
257 // glob imports arent part of the use-tree lists so we need to special handle them here as well
258 // this special handling is only required for when we merge a module import into a glob import of said module
259 // see the `merge_self_glob` or `merge_mod_into_glob` tests
260 if use_trees[idx].star_token().is_some() || rhs_t.star_token().is_some() {
261 use_trees[idx] = make::use_tree(
262 make::path_unqualified(make::path_segment_self()),
263 None,
264 None,
265 false,
266 );
267 use_trees.insert(
268 idx,
269 make::use_tree(
270 make::path_unqualified(make::path_segment_self()),
271 None,
272 None,
273 true,
274 ),
275 );
276 continue;
277 }
278 }
279 let lhs = use_trees[idx].split_prefix(&lhs_prefix);
280 let rhs = rhs_t.split_prefix(&rhs_prefix);
281 match recursive_merge(&lhs, &rhs, merge) {
282 Some((_, count))
283 if merge == MergeBehaviour::Last && use_trees.len() > 1 && count > 1 =>
284 {
285 return None
286 }
287 Some((use_tree, _)) => use_trees[idx] = use_tree,
288 None => use_trees.insert(idx, rhs_t),
289 }
290 }
291 Err(idx) => {
292 use_trees.insert(idx, rhs_t);
293 }
294 }
295 }
296 let count = use_trees.len();
297 let tl = make::use_tree_list(use_trees);
298 Some((lhs.with_use_tree_list(tl), count))
212} 299}
213 300
214/// Traverses both paths until they differ, returning the common prefix of both. 301/// Traverses both paths until they differ, returning the common prefix of both.
@@ -235,6 +322,23 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa
235 res 322 res
236} 323}
237 324
325fn path_is_self(path: ast::Path) -> bool {
326 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
327}
328
329fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
330 first_path(path).segment()
331}
332
333fn first_path(path: &ast::Path) -> ast::Path {
334 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
335}
336
337fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
338 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
339 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
340}
341
238/// What type of merges are allowed. 342/// What type of merges are allowed.
239#[derive(Copy, Clone, PartialEq, Eq)] 343#[derive(Copy, Clone, PartialEq, Eq)]
240pub enum MergeBehaviour { 344pub enum MergeBehaviour {
@@ -279,19 +383,6 @@ impl ImportGroup {
279 } 383 }
280} 384}
281 385
282fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
283 first_path(path).segment()
284}
285
286fn first_path(path: &ast::Path) -> ast::Path {
287 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
288}
289
290fn 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)] 386#[derive(PartialEq, Eq)]
296enum AddBlankLine { 387enum AddBlankLine {
297 Before, 388 Before,
@@ -594,7 +685,7 @@ use std::io;",
594 check_full( 685 check_full(
595 "std::foo::bar::Baz", 686 "std::foo::bar::Baz",
596 r"use std::foo::bar::Qux;", 687 r"use std::foo::bar::Qux;",
597 r"use std::foo::bar::{Qux, Baz};", 688 r"use std::foo::bar::{Baz, Qux};",
598 ) 689 )
599 } 690 }
600 691
@@ -603,7 +694,7 @@ use std::io;",
603 check_last( 694 check_last(
604 "std::foo::bar::Baz", 695 "std::foo::bar::Baz",
605 r"use std::foo::bar::Qux;", 696 r"use std::foo::bar::Qux;",
606 r"use std::foo::bar::{Qux, Baz};", 697 r"use std::foo::bar::{Baz, Qux};",
607 ) 698 )
608 } 699 }
609 700
@@ -612,7 +703,7 @@ use std::io;",
612 check_full( 703 check_full(
613 "std::foo::bar::Baz", 704 "std::foo::bar::Baz",
614 r"use std::foo::bar::{Qux, Quux};", 705 r"use std::foo::bar::{Qux, Quux};",
615 r"use std::foo::bar::{Qux, Quux, Baz};", 706 r"use std::foo::bar::{Baz, Quux, Qux};",
616 ) 707 )
617 } 708 }
618 709
@@ -621,7 +712,7 @@ use std::io;",
621 check_last( 712 check_last(
622 "std::foo::bar::Baz", 713 "std::foo::bar::Baz",
623 r"use std::foo::bar::{Qux, Quux};", 714 r"use std::foo::bar::{Qux, Quux};",
624 r"use std::foo::bar::{Qux, Quux, Baz};", 715 r"use std::foo::bar::{Baz, Quux, Qux};",
625 ) 716 )
626 } 717 }
627 718
@@ -630,7 +721,7 @@ use std::io;",
630 check_full( 721 check_full(
631 "std::foo::bar::Baz", 722 "std::foo::bar::Baz",
632 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 723 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
633 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};", 724 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
634 ) 725 )
635 } 726 }
636 727
@@ -645,6 +736,15 @@ use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
645 } 736 }
646 737
647 #[test] 738 #[test]
739 fn merge_groups_full_nested_deep() {
740 check_full(
741 "std::foo::bar::quux::Baz",
742 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
743 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
744 )
745 }
746
747 #[test]
648 fn merge_groups_skip_pub() { 748 fn merge_groups_skip_pub() {
649 check_full( 749 check_full(
650 "std::io", 750 "std::io",
@@ -670,34 +770,63 @@ use std::io;",
670 check_last( 770 check_last(
671 "std::fmt::Result", 771 "std::fmt::Result",
672 r"use std::{fmt, io};", 772 r"use std::{fmt, io};",
673 r"use std::{self, fmt::Result}; 773 r"use std::fmt::{self, Result};
674use std::io;", 774use std::io;",
675 ) 775 )
676 } 776 }
677 777
678 #[test] 778 #[test]
779 fn merge_into_module_import() {
780 check_full(
781 "std::fmt::Result",
782 r"use std::{fmt, io};",
783 r"use std::{fmt::{self, Result}, io};",
784 )
785 }
786
787 #[test]
679 fn merge_groups_self() { 788 fn merge_groups_self() {
680 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") 789 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
681 } 790 }
682 791
683 #[test] 792 #[test]
684 fn merge_self_glob() { 793 fn merge_mod_into_glob() {
685 check_full( 794 check_full(
686 "token::TokenKind", 795 "token::TokenKind",
687 r"use token::TokenKind::*;", 796 r"use token::TokenKind::*;",
688 r"use token::TokenKind::{self::*, self};", 797 r"use token::TokenKind::{self::*, self};",
689 ) 798 )
799 // FIXME: have it emit `use token::TokenKind::{self, *}`?
800 }
801
802 #[test]
803 fn merge_self_glob() {
804 check_full("self", r"use self::*;", r"use self::{self::*, self};")
805 // FIXME: have it emit `use {self, *}`?
806 }
807
808 #[test]
809 #[ignore] // FIXME: Support this
810 fn merge_partial_path() {
811 check_full(
812 "ast::Foo",
813 r"use syntax::{ast, algo};",
814 r"use syntax::{ast::{self, Foo}, algo};",
815 )
816 }
817
818 #[test]
819 fn merge_glob_nested() {
820 check_full(
821 "foo::bar::quux::Fez",
822 r"use foo::bar::{Baz, quux::*;",
823 r"use foo::bar::{Baz, quux::{self::*, Fez}}",
824 )
690 } 825 }
691 826
692 #[test] 827 #[test]
693 fn merge_last_too_long() { 828 fn merge_last_too_long() {
694 mark::check!(test_last_merge_too_long); 829 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;
699use foo::bar::baz::Qux;",
700 );
701 } 830 }
702 831
703 #[test] 832 #[test]
@@ -710,6 +839,42 @@ use foo::bar::baz::Qux;",
710 ); 839 );
711 } 840 }
712 841
842 #[test]
843 fn merge_last_fail() {
844 check_merge_only_fail(
845 r"use foo::bar::{baz::{Qux, Fez}};",
846 r"use foo::bar::{baaz::{Quux, Feez}};",
847 MergeBehaviour::Last,
848 );
849 }
850
851 #[test]
852 fn merge_last_fail1() {
853 check_merge_only_fail(
854 r"use foo::bar::{baz::{Qux, Fez}};",
855 r"use foo::bar::baaz::{Quux, Feez};",
856 MergeBehaviour::Last,
857 );
858 }
859
860 #[test]
861 fn merge_last_fail2() {
862 check_merge_only_fail(
863 r"use foo::bar::baz::{Qux, Fez};",
864 r"use foo::bar::{baaz::{Quux, Feez}};",
865 MergeBehaviour::Last,
866 );
867 }
868
869 #[test]
870 fn merge_last_fail3() {
871 check_merge_only_fail(
872 r"use foo::bar::baz::{Qux, Fez};",
873 r"use foo::bar::baaz::{Quux, Feez};",
874 MergeBehaviour::Last,
875 );
876 }
877
713 fn check( 878 fn check(
714 path: &str, 879 path: &str,
715 ra_fixture_before: &str, 880 ra_fixture_before: &str,
@@ -742,4 +907,23 @@ use foo::bar::baz::Qux;",
742 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 907 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
743 check(path, ra_fixture_before, ra_fixture_after, None) 908 check(path, ra_fixture_before, ra_fixture_after, None)
744 } 909 }
910
911 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
912 let use0 = ast::SourceFile::parse(ra_fixture0)
913 .tree()
914 .syntax()
915 .descendants()
916 .find_map(ast::Use::cast)
917 .unwrap();
918
919 let use1 = ast::SourceFile::parse(ra_fixture1)
920 .tree()
921 .syntax()
922 .descendants()
923 .find_map(ast::Use::cast)
924 .unwrap();
925
926 let result = try_merge_imports(&use0, &use1, mb);
927 assert_eq!(result, None);
928 }
745} 929}
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8b1c65dd6..45cf31f13 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -347,6 +347,7 @@ impl ast::UseTree {
347 self.clone() 347 self.clone()
348 } 348 }
349 349
350 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
350 #[must_use] 351 #[must_use]
351 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { 352 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
352 let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() { 353 let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() {