]> git.lizzy.rs Git - rust.git/commitdiff
Use mutable syntax trees in `merge_imports`, `split_imports`
authoriDawer <ilnur.iskhakov.oss@outlook.com>
Fri, 19 Nov 2021 09:06:36 +0000 (14:06 +0500)
committeriDawer <ilnur.iskhakov.oss@outlook.com>
Fri, 19 Nov 2021 15:02:27 +0000 (20:02 +0500)
crates/ide_assists/src/handlers/merge_imports.rs
crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
crates/ide_assists/src/handlers/split_import.rs
crates/ide_db/src/helpers/insert_use/tests.rs
crates/ide_db/src/helpers/merge_imports.rs
crates/syntax/src/ast/edit.rs
crates/syntax/src/ast/edit_in_place.rs

index fc117bd9a8469c6eb676977512eac5bd1211ca10..94400be7a5bddfc521c3e11a1a46e4846f3c236a 100644 (file)
@@ -80,7 +80,7 @@ fn test_merge_equal() {
 use std::fmt::{Display, Debug};
 ",
             r"
-use std::fmt::{Debug, Display};
+use std::fmt::{Display, Debug};
 ",
         )
     }
@@ -108,7 +108,7 @@ fn test_merge_second() {
 use std::fmt$0::Display;
 ",
             r"
-use std::fmt::{Debug, Display};
+use std::fmt::{Display, Debug};
 ",
         );
     }
@@ -135,7 +135,7 @@ fn merge_self2() {
 use std::{fmt, $0fmt::Display};
 ",
             r"
-use std::{fmt::{self, Display}};
+use std::{fmt::{Display, self}};
 ",
         );
     }
@@ -247,7 +247,7 @@ fn test_merge_nested2() {
 use std::{fmt::Debug, fmt$0::Display};
 ",
             r"
-use std::{fmt::{Debug, Display}};
+use std::{fmt::{Display, Debug}};
 ",
         );
     }
@@ -341,7 +341,9 @@ fn test_double_comma() {
 };
 ",
             r"
-use foo::{FooBar, bar::baz};
+use foo::{
+    FooBar, bar::baz,
+};
 ",
         )
     }
index 8df8c4b726fed662a5515ecfef4565c46447d30c..6b196681cab0936d2e0491803fa111ad75e30df8 100644 (file)
@@ -299,7 +299,7 @@ fn main() {
     ",
             r"
 mod std { pub mod fmt { pub trait Display {} } }
-use std::fmt::{self, Display};
+use std::fmt::{Display, self};
 
 fn main() {
     fmt;
index 090df25cbde731b6cacb7b2ca33b7d7211efb3a9..ac67874b31bdaf752caf01549d25a53b3c487cc5 100644 (file)
@@ -19,14 +19,20 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
 
     let use_tree = path.top_path().syntax().ancestors().find_map(ast::UseTree::cast)?;
 
-    let new_tree = use_tree.split_prefix(&path);
-    if new_tree == use_tree {
+    let has_errors = use_tree
+        .syntax()
+        .descendants_with_tokens()
+        .any(|it| it.kind() == syntax::SyntaxKind::ERROR);
+    let last_segment = use_tree.path().and_then(|it| it.segment());
+    if has_errors || last_segment.is_none() {
         return None;
     }
 
     let target = colon_colon.text_range();
     acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
-        edit.replace_ast(use_tree, new_tree);
+        let use_tree = edit.make_mut(use_tree.clone());
+        let path = edit.make_mut(path);
+        use_tree.split_prefix(&path);
     })
 }
 
index 34a6900e2673e619e33b3983d0fc575decf1b0a2..a5763eb41ccbb69e135a5235d2965fd16e922a19 100644 (file)
@@ -463,7 +463,7 @@ fn merge_groups_full() {
 
 #[test]
 fn merge_groups_long_full() {
-    check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};")
+    check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Qux, Baz};")
 }
 
 #[test]
@@ -471,7 +471,7 @@ fn merge_groups_long_last() {
     check_module(
         "std::foo::bar::Baz",
         r"use std::foo::bar::Qux;",
-        r"use std::foo::bar::{Baz, Qux};",
+        r"use std::foo::bar::{Qux, Baz};",
     )
 }
 
@@ -480,7 +480,7 @@ fn merge_groups_long_full_list() {
     check_crate(
         "std::foo::bar::Baz",
         r"use std::foo::bar::{Qux, Quux};",
-        r"use std::foo::bar::{Baz, Quux, Qux};",
+        r"use std::foo::bar::{Qux, Quux, Baz};",
     )
 }
 
@@ -489,7 +489,7 @@ fn merge_groups_long_last_list() {
     check_module(
         "std::foo::bar::Baz",
         r"use std::foo::bar::{Qux, Quux};",
-        r"use std::foo::bar::{Baz, Quux, Qux};",
+        r"use std::foo::bar::{Qux, Quux, Baz};",
     )
 }
 
@@ -498,7 +498,7 @@ fn merge_groups_long_full_nested() {
     check_crate(
         "std::foo::bar::Baz",
         r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
-        r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
+        r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};",
     )
 }
 
@@ -517,7 +517,7 @@ fn merge_groups_full_nested_deep() {
     check_crate(
         "std::foo::bar::quux::Baz",
         r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
-        r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
+        r"use std::foo::bar::{Qux, quux::{Fez, Fizz, Baz}};",
     )
 }
 
@@ -526,7 +526,7 @@ fn merge_groups_full_nested_long() {
     check_crate(
         "std::foo::bar::Baz",
         r"use std::{foo::bar::Qux};",
-        r"use std::{foo::bar::{Baz, Qux}};",
+        r"use std::{foo::bar::{Qux, Baz}};",
     );
 }
 
@@ -535,7 +535,7 @@ fn merge_groups_last_nested_long() {
     check_crate(
         "std::foo::bar::Baz",
         r"use std::{foo::bar::Qux};",
-        r"use std::{foo::bar::{Baz, Qux}};",
+        r"use std::{foo::bar::{Qux, Baz}};",
     );
 }
 
@@ -600,7 +600,7 @@ fn merge_mod_into_glob() {
     check_with_config(
         "token::TokenKind",
         r"use token::TokenKind::*;",
-        r"use token::TokenKind::{*, self};",
+        r"use token::TokenKind::{self, *};",
         &InsertUseConfig {
             granularity: ImportGranularity::Crate,
             enforce_granularity: true,
@@ -618,7 +618,7 @@ fn merge_self_glob() {
     check_with_config(
         "self",
         r"use self::*;",
-        r"use self::{*, self};",
+        r"use self::{self, *};",
         &InsertUseConfig {
             granularity: ImportGranularity::Crate,
             enforce_granularity: true,
@@ -637,7 +637,7 @@ fn merge_glob() {
         r"
 use syntax::{SyntaxKind::*};",
         r"
-use syntax::{SyntaxKind::{*, self}};",
+use syntax::{SyntaxKind::{self, *}};",
     )
 }
 
index 6a7a9b8f61c689bb8eed7d947c76702e2974339a..8581a83436135703198801ec26f8749869264b4a 100644 (file)
@@ -44,10 +44,10 @@ pub fn try_merge_imports(
     }
 
     let lhs = lhs.clone_subtree().clone_for_update();
+    let rhs = rhs.clone_subtree().clone_for_update();
     let lhs_tree = lhs.use_tree()?;
     let rhs_tree = rhs.use_tree()?;
-    let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
-    ted::replace(lhs_tree.syntax(), merged.syntax());
+    try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
     Some(lhs)
 }
 
@@ -56,57 +56,49 @@ pub fn try_merge_trees(
     rhs: &ast::UseTree,
     merge: MergeBehavior,
 ) -> Option<ast::UseTree> {
+    let lhs = lhs.clone_subtree().clone_for_update();
+    let rhs = rhs.clone_subtree().clone_for_update();
+    try_merge_trees_mut(&lhs, &rhs, merge)?;
+    Some(lhs)
+}
+
+fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
     let lhs_path = lhs.path()?;
     let rhs_path = rhs.path()?;
 
     let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
-    let (lhs, rhs) = if lhs.is_simple_path()
+    if !(lhs.is_simple_path()
         && rhs.is_simple_path()
         && lhs_path == lhs_prefix
-        && rhs_path == rhs_prefix
+        && rhs_path == rhs_prefix)
     {
-        (lhs.clone(), rhs.clone())
-    } else {
-        (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
-    };
-    recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
+        lhs.split_prefix(&lhs_prefix);
+        rhs.split_prefix(&rhs_prefix);
+    }
+    recursive_merge(&lhs, &rhs, merge)
 }
 
-/// Recursively "zips" together lhs and rhs.
-fn recursive_merge(
-    lhs: &ast::UseTree,
-    rhs: &ast::UseTree,
-    merge: MergeBehavior,
-) -> Option<ast::UseTree> {
-    let mut use_trees = lhs
+/// Recursively merges rhs to lhs
+#[must_use]
+fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+    let mut use_trees: Vec<ast::UseTree> = lhs
         .use_tree_list()
         .into_iter()
         .flat_map(|list| list.use_trees())
         // We use Option here to early return from this function(this is not the
         // same as a `filter` op).
-        .map(|tree| match merge.is_tree_allowed(&tree) {
-            true => Some(tree),
-            false => None,
-        })
-        .collect::<Option<Vec<_>>>()?;
+        .map(|tree| merge.is_tree_allowed(&tree).then(|| tree))
+        .collect::<Option<_>>()?;
     use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
     for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
         if !merge.is_tree_allowed(&rhs_t) {
             return None;
         }
         let rhs_path = rhs_t.path();
-        match use_trees.binary_search_by(|lhs_t| {
-            let (lhs_t, rhs_t) = match lhs_t
-                .path()
-                .zip(rhs_path.clone())
-                .and_then(|(lhs, rhs)| common_prefix(&lhs, &rhs))
-            {
-                Some((lhs_p, rhs_p)) => (lhs_t.split_prefix(&lhs_p), rhs_t.split_prefix(&rhs_p)),
-                None => (lhs_t.clone(), rhs_t.clone()),
-            };
 
-            path_cmp_bin_search(lhs_t.path(), rhs_t.path())
-        }) {
+        match use_trees
+            .binary_search_by(|lhs_t| path_cmp_bin_search(lhs_t.path(), rhs_path.as_ref()))
+        {
             Ok(idx) => {
                 let lhs_t = &mut use_trees[idx];
                 let lhs_path = lhs_t.path()?;
@@ -128,6 +120,7 @@ fn recursive_merge(
                     match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
                         (true, false) => continue,
                         (false, true) => {
+                            ted::replace(lhs_t.syntax(), rhs_t.syntax());
                             *lhs_t = rhs_t;
                             continue;
                         }
@@ -142,25 +135,27 @@ fn recursive_merge(
                     if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
                         if tree_is_self(lhs_t) || tree_is_self(&rhs_t) {
                             cov_mark::hit!(merge_self_glob);
-                            *lhs_t = make::use_tree(
+                            let self_tree = make::use_tree(
                                 make::path_unqualified(make::path_segment_self()),
                                 None,
                                 None,
                                 false,
-                            );
-                            use_trees.insert(idx, make::use_tree_glob());
+                            )
+                            .clone_for_update();
+                            ted::replace(lhs_t.syntax(), self_tree.syntax());
+                            *lhs_t = self_tree;
+                            let glob = make::use_tree_glob().clone_for_update();
+                            use_trees.insert(idx, glob.clone());
+                            lhs.get_or_create_use_tree_list().add_use_tree(glob);
                             continue;
                         }
                     } else if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
                         continue;
                     }
                 }
-                let lhs = lhs_t.split_prefix(&lhs_prefix);
-                let rhs = rhs_t.split_prefix(&rhs_prefix);
-                match recursive_merge(&lhs, &rhs, merge) {
-                    Some(use_tree) => use_trees[idx] = use_tree,
-                    None => return None,
-                }
+                lhs_t.split_prefix(&lhs_prefix);
+                rhs_t.split_prefix(&rhs_prefix);
+                recursive_merge(&lhs_t, &rhs_t, merge)?;
             }
             Err(_)
                 if merge == MergeBehavior::Module
@@ -170,16 +165,12 @@ fn recursive_merge(
                 return None
             }
             Err(idx) => {
-                use_trees.insert(idx, rhs_t);
+                use_trees.insert(idx, rhs_t.clone());
+                lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
             }
         }
     }
-
-    let lhs = lhs.clone_subtree().clone_for_update();
-    if let Some(old) = lhs.use_tree_list() {
-        ted::replace(old.syntax(), make::use_tree_list(use_trees).syntax().clone_for_update());
-    }
-    ast::UseTree::cast(lhs.syntax().clone_subtree())
+    Some(())
 }
 
 /// Traverses both paths until they differ, returning the common prefix of both.
@@ -224,11 +215,9 @@ fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
 }
 
 /// Path comparison func for binary searching for merging.
-fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
-    match (
-        lhs.as_ref().and_then(ast::Path::first_segment),
-        rhs.as_ref().and_then(ast::Path::first_segment),
-    ) {
+fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<&ast::Path>) -> Ordering {
+    match (lhs.as_ref().and_then(ast::Path::first_segment), rhs.and_then(ast::Path::first_segment))
+    {
         (None, None) => Ordering::Equal,
         (None, Some(_)) => Ordering::Less,
         (Some(_), None) => Ordering::Greater,
index 43a9c6756d576dba7ed2229462ae3ac3cc756292..15805dfc8608feba4429576eee1bb3e3db28fc2c 100644 (file)
@@ -4,48 +4,10 @@
 use std::{fmt, iter, ops};
 
 use crate::{
-    algo,
     ast::{self, make, AstNode},
     ted, AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
 };
 
-impl ast::UseTree {
-    /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
-    #[must_use]
-    pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
-        let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() {
-            make::path_unqualified(make::path_segment_self())
-        } else {
-            match split_path_prefix(prefix) {
-                Some(it) => it,
-                None => return self.clone(),
-            }
-        };
-
-        let use_tree = make::use_tree(
-            suffix,
-            self.use_tree_list(),
-            self.rename(),
-            self.star_token().is_some(),
-        );
-        let nested = make::use_tree_list(iter::once(use_tree));
-        return make::use_tree(prefix.clone(), Some(nested), None, false);
-
-        fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> {
-            let parent = prefix.parent_path()?;
-            let segment = parent.segment()?;
-            if algo::has_errors(segment.syntax()) {
-                return None;
-            }
-            let mut res = make::path_unqualified(segment);
-            for p in iter::successors(parent.parent_path(), |it| it.parent_path()) {
-                res = make::path_qualified(res, p.segment()?);
-            }
-            Some(res)
-        }
-    }
-}
-
 #[derive(Debug, Clone, Copy)]
 pub struct IndentLevel(pub u8);
 
index 552d6fc1e3ebbf699355dd0ef0cc4210e819fffe..bf4f9d6d18b71aa01f7c9e5a2ff5e0e0f0c3c0c5 100644 (file)
@@ -1,12 +1,12 @@
 //! Structural editing for ast.
 
-use std::iter::empty;
+use std::iter::{empty, successors};
 
 use parser::{SyntaxKind, T};
 use rowan::SyntaxElement;
 
 use crate::{
-    algo::neighbor,
+    algo::{self, neighbor},
     ast::{self, edit::IndentLevel, make, HasGenericParams},
     ted::{self, Position},
     AstNode, AstToken, Direction,
@@ -282,6 +282,78 @@ pub fn remove(&self) {
         }
         ted::remove(self.syntax());
     }
+
+    pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList {
+        match self.use_tree_list() {
+            Some(it) => it,
+            None => {
+                let position = Position::last_child_of(self.syntax());
+                let use_tree_list = make::use_tree_list(empty()).clone_for_update();
+                let mut elements = Vec::with_capacity(2);
+                if self.coloncolon_token().is_none() {
+                    elements.push(make::token(T![::]).into());
+                }
+                elements.push(use_tree_list.syntax().clone().into());
+                ted::insert_all_raw(position, elements);
+                use_tree_list
+            }
+        }
+    }
+
+    /// Splits off the given prefix, making it the path component of the use tree,
+    /// appending the rest of the path to all UseTreeList items.
+    pub fn split_prefix(&self, prefix: &ast::Path) {
+        debug_assert_eq!(self.path(), Some(prefix.top_path()));
+        let path = self.path().unwrap();
+        if &path == prefix && self.use_tree_list().is_none() {
+            let self_suffix = make::path_unqualified(make::path_segment_self()).clone_for_update();
+            ted::replace(path.syntax(), self_suffix.syntax());
+        } else if split_path_prefix(prefix).is_none() {
+            return;
+        }
+
+        let subtree = self.clone_subtree().clone_for_update();
+        ted::remove_all_iter(self.syntax().children_with_tokens());
+        ted::insert(Position::first_child_of(self.syntax()), prefix.syntax());
+        self.get_or_create_use_tree_list().add_use_tree(subtree);
+
+        fn split_path_prefix(prefix: &ast::Path) -> Option<()> {
+            let parent = prefix.parent_path()?;
+            let segment = parent.segment()?;
+            if algo::has_errors(segment.syntax()) {
+                return None;
+            }
+            for p in successors(parent.parent_path(), |it| it.parent_path()) {
+                p.segment()?;
+            }
+            prefix.parent_path().and_then(|p| p.coloncolon_token()).map(ted::remove);
+            ted::remove(prefix.syntax());
+            Some(())
+        }
+    }
+}
+
+impl ast::UseTreeList {
+    pub fn add_use_tree(&self, use_tree: ast::UseTree) {
+        let (position, elements) = match self.use_trees().last() {
+            Some(last_tree) => (
+                Position::after(last_tree.syntax()),
+                vec![
+                    make::token(T![,]).into(),
+                    make::tokens::single_space().into(),
+                    use_tree.syntax.into(),
+                ],
+            ),
+            None => {
+                let position = match self.l_curly_token() {
+                    Some(l_curly) => Position::after(l_curly),
+                    None => Position::last_child_of(self.syntax()),
+                };
+                (position, vec![use_tree.syntax.into()])
+            }
+        };
+        ted::insert_all_raw(position, elements);
+    }
 }
 
 impl ast::Use {