Repository: rust-analyzer/rust-analyzer
Pull request #10439 (2021-10-03 11:53:20)

fix: fix insert_use incorrectly merging glob imports
Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6800
bors r+


Patch:
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index cce342272f..a495e6c543 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -1899,51 +1899,6 @@ fn func$0() {
 fn function() {
     function();
 }
-"#,
-        )
-    }
-
-    #[test]
-    fn in_macro_multi_mapping() {
-        check(
-            "a",
-            r#"
-fn foo() {
-    macro_rules! match_ast2 {
-        ($node:ident {
-            $( $res:expr, )*
-        }) => {{
-            $( if $node { $res } else )*
-            { loop {} }
-        }};
-    }
-    let $0d = 3;
-    match_ast2! {
-        d {
-            d,
-            d,
-        }
-    };
-}
-"#,
-            r#"
-fn foo() {
-    macro_rules! match_ast2 {
-        ($node:ident {
-            $( $res:expr, )*
-        }) => {{
-            $( if $node { $res } else )*
-            { loop {} }
-        }};
-    }
-    let a = 3;
-    match_ast2! {
-        a {
-            a,
-            a,
-        }
-    };
-}
 "#,
         )
     }
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 2e91f8d4ab..585ef31daf 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -1090,7 +1090,7 @@ fn make_call(ctx: &AssistContext, fun: &Function, indent: IndentLevel) -> String
 
     let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx)));
     let name = fun.name.clone();
-    let mut call_expr = if fun.self_param.is_some() {
+    let call_expr = if fun.self_param.is_some() {
         let self_arg = make::expr_path(make::ext::ident_path("self"));
         make::expr_method_call(self_arg, name, args)
     } else {
@@ -1100,9 +1100,6 @@ fn make_call(ctx: &AssistContext, fun: &Function, indent: IndentLevel) -> String
 
     let handler = FlowHandler::from_ret_ty(fun, &ret_ty);
 
-    if fun.control_flow.is_async {
-        call_expr = make::expr_await(call_expr);
-    }
     let expr = handler.make_call_expr(call_expr).indent(indent);
 
     let mut_modifier = |var: &OutlivedLocal| if var.mut_usage_outside_body { "mut " } else { "" };
@@ -1122,8 +1119,10 @@ fn make_call(ctx: &AssistContext, fun: &Function, indent: IndentLevel) -> String
             buf.push_str(") = ");
         }
     }
-
     format_to!(buf, "{}", expr);
+    if fun.control_flow.is_async {
+        buf.push_str(".await");
+    }
     let insert_comma = fun
         .body
         .parent()
@@ -3871,70 +3870,6 @@ async fn $0fun_name() {
 
 async fn some_function() {
 
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn extract_with_await_and_result_not_producing_match_expr() {
-        check_assist(
-            extract_function,
-            r#"
-async fn foo() -> Result<(), ()> {
-    $0async {}.await;
-    Err(())?$0
-}
-"#,
-            r#"
-async fn foo() -> Result<(), ()> {
-    fun_name().await?
-}
-
-async fn $0fun_name() -> _ {
-    async {}.await;
-    Err(())?
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn extract_with_await_and_result_producing_match_expr() {
-        check_assist(
-            extract_function,
-            r#"
-async fn foo() -> i32 {
-    loop {
-        let n = 1;$0
-        let k = async { 1 }.await;
-        if k == 42 {
-            break 3;
-        }
-        let m = k + 1;$0
-        let h = 1 + m;
-    }
-}
-"#,
-            r#"
-async fn foo() -> i32 {
-    loop {
-        let n = 1;
-        let m = match fun_name().await {
-            Ok(value) => value,
-            Err(value) => break value,
-        };
-        let h = 1 + m;
-    }
-}
-
-async fn $0fun_name() -> Result<i32, i32> {
-    let k = async { 1 }.await;
-    if k == 42 {
-        return Err(3);
-    }
-    let m = k + 1;
-    Ok(m)
 }
 "#,
         );
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index 55e5da7fe7..9b4e1380a4 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -1,7 +1,6 @@
 use stdx::format_to;
 use syntax::{
     ast::{self, AstNode},
-    NodeOrToken,
     SyntaxKind::{
         BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, MATCH_GUARD,
         PATH_EXPR, RETURN_EXPR,
@@ -31,26 +30,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
     if ctx.frange.range.is_empty() {
         return None;
     }
-    let node = match ctx.covering_element() {
-        NodeOrToken::Node(it) => it,
-        NodeOrToken::Token(it) if it.kind() == COMMENT => {
-            cov_mark::hit!(extract_var_in_comment_is_not_applicable);
-            return None;
-        }
-        NodeOrToken::Token(it) => it.parent()?,
-    };
-    let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?;
+    let node = ctx.covering_element();
+    if node.kind() == COMMENT {
+        cov_mark::hit!(extract_var_in_comment_is_not_applicable);
+        return None;
+    }
     let to_extract = node
-        .descendants()
-        .take_while(|it| ctx.frange.range.contains_range(it.text_range()))
+        .ancestors()
+        .take_while(|it| it.text_range().contains_range(ctx.frange.range))
         .find_map(valid_target_expr)?;
-
     if let Some(ty_info) = ctx.sema.type_of_expr(&to_extract) {
         if ty_info.adjusted().is_unit() {
             return None;
         }
     }
-
     let anchor = Anchor::from(&to_extract)?;
     let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
     let target = to_extract.syntax().text_range();
@@ -153,11 +146,8 @@ enum Anchor {
 
 impl Anchor {
     fn from(to_extract: &ast::Expr) -> Option<Anchor> {
-        to_extract
-            .syntax()
-            .ancestors()
-            .take_while(|it| !ast::Item::can_cast(it.kind()) || ast::MacroCall::can_cast(it.kind()))
-            .find_map(|node| {
+        to_extract.syntax().ancestors().take_while(|it| !ast::Item::can_cast(it.kind())).find_map(
+            |node| {
                 if let Some(expr) =
                     node.parent().and_then(ast::StmtList::cast).and_then(|it| it.tail_expr())
                 {
@@ -191,7 +181,8 @@ impl Anchor {
                     return Some(Anchor::Before(node));
                 }
                 None
-            })
+            },
+        )
     }
 
     fn syntax(&self) -> &SyntaxNode {
diff --git a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
index 22675fbee0..3a89c2b996 100644
--- a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
@@ -210,7 +210,11 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext)
                 ast::Expr::BlockExpr(block) => block,
                 expr => make::block_expr(iter::empty(), Some(expr)),
             };
-            let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
+            let else_expr = match else_expr {
+                ast::Expr::BlockExpr(block) if block.is_empty() => None,
+                ast::Expr::TupleExpr(tuple) if tuple.fields().next().is_none() => None,
+                expr => Some(expr),
+            };
             let if_let_expr = make::expr_if(
                 condition,
                 then_block,
@@ -253,10 +257,7 @@ fn pick_pattern_and_expr_order(
 
 fn is_empty_expr(expr: &ast::Expr) -> bool {
     match expr {
-        ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
-            Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
-            None => true,
-        },
+        ast::Expr::BlockExpr(expr) => expr.is_empty(),
         ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
         _ => false,
     }
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index a5d1932775..9889dd772e 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -1,4 +1,4 @@
-//! Handle syntactic aspects of inserting a new `use`.
+//! Handle syntactic aspects of inserting a new `use` item.
 #[cfg(test)]
 mod tests;
 
@@ -103,81 +103,6 @@ impl ImportScope {
             ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
         }
     }
-
-    fn guess_granularity_from_scope(&self) -> ImportGranularityGuess {
-        // The idea is simple, just check each import as well as the import and its precedent together for
-        // whether they fulfill a granularity criteria.
-        let use_stmt = |item| match item {
-            ast::Item::Use(use_) => {
-                let use_tree = use_.use_tree()?;
-                Some((use_tree, use_.visibility(), use_.attrs()))
-            }
-            _ => None,
-        };
-        let mut use_stmts = match self {
-            ImportScope::File(f) => f.items(),
-            ImportScope::Module(m) => m.items(),
-            ImportScope::Block(b) => b.items(),
-        }
-        .filter_map(use_stmt);
-        let mut res = ImportGranularityGuess::Unknown;
-        let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() {
-            Some(it) => it,
-            None => return res,
-        };
-        loop {
-            if let Some(use_tree_list) = prev.use_tree_list() {
-                if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
-                    // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
-                    break ImportGranularityGuess::Crate;
-                } else {
-                    // Could still be crate-style so continue looking.
-                    res = ImportGranularityGuess::CrateOrModule;
-                }
-            }
-
-            let (curr, curr_vis, curr_attrs) = match use_stmts.next() {
-                Some(it) => it,
-                None => break res,
-            };
-            if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone())
-            {
-                if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
-                    if let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path) {
-                        if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
-                            let prefix_c = prev_prefix.qualifiers().count();
-                            let curr_c = curr_path.qualifiers().count() - prefix_c;
-                            let prev_c = prev_path.qualifiers().count() - prefix_c;
-                            if curr_c == 1 && prev_c == 1 {
-                                // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
-                                break ImportGranularityGuess::Item;
-                            } else {
-                                // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
-                                res = ImportGranularityGuess::ModuleOrItem;
-                            }
-                        } else {
-                            // Same prefix with item tree lists, has to be module style as it
-                            // can't be crate style since the trees wouldn't share a prefix then.
-                            break ImportGranularityGuess::Module;
-                        }
-                    }
-                }
-            }
-            prev = curr;
-            prev_vis = curr_vis;
-            prev_attrs = curr_attrs;
-        }
-    }
-}
-
-#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
-enum ImportGranularityGuess {
-    Unknown,
-    Item,
-    Module,
-    ModuleOrItem,
-    Crate,
-    CrateOrModule,
 }
 
 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
@@ -189,7 +114,7 @@ pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
         ImportGranularity::Item | ImportGranularity::Preserve => None,
     };
     if !cfg.enforce_granularity {
-        let file_granularity = scope.guess_granularity_from_scope();
+        let file_granularity = guess_granularity_from_scope(scope);
         mb = match file_granularity {
             ImportGranularityGuess::Unknown => mb,
             ImportGranularityGuess::Item => None,
@@ -271,6 +196,80 @@ impl ImportGroup {
     }
 }
 
+#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
+enum ImportGranularityGuess {
+    Unknown,
+    Item,
+    Module,
+    ModuleOrItem,
+    Crate,
+    CrateOrModule,
+}
+
+fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
+    // The idea is simple, just check each import as well as the import and its precedent together for
+    // whether they fulfill a granularity criteria.
+    let use_stmt = |item| match item {
+        ast::Item::Use(use_) => {
+            let use_tree = use_.use_tree()?;
+            Some((use_tree, use_.visibility(), use_.attrs()))
+        }
+        _ => None,
+    };
+    let mut use_stmts = match scope {
+        ImportScope::File(f) => f.items(),
+        ImportScope::Module(m) => m.items(),
+        ImportScope::Block(b) => b.items(),
+    }
+    .filter_map(use_stmt);
+    let mut res = ImportGranularityGuess::Unknown;
+    let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() {
+        Some(it) => it,
+        None => return res,
+    };
+    loop {
+        if let Some(use_tree_list) = prev.use_tree_list() {
+            if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
+                // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
+                break ImportGranularityGuess::Crate;
+            } else {
+                // Could still be crate-style so continue looking.
+                res = ImportGranularityGuess::CrateOrModule;
+            }
+        }
+
+        let (curr, curr_vis, curr_attrs) = match use_stmts.next() {
+            Some(it) => it,
+            None => break res,
+        };
+        if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone()) {
+            if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
+                if let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path) {
+                    if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
+                        let prefix_c = prev_prefix.qualifiers().count();
+                        let curr_c = curr_path.qualifiers().count() - prefix_c;
+                        let prev_c = prev_path.qualifiers().count() - prefix_c;
+                        if curr_c == 1 && prev_c == 1 {
+                            // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
+                            break ImportGranularityGuess::Item;
+                        } else {
+                            // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
+                            res = ImportGranularityGuess::ModuleOrItem;
+                        }
+                    } else {
+                        // Same prefix with item tree lists, has to be module style as it
+                        // can't be crate style since the trees wouldn't share a prefix then.
+                        break ImportGranularityGuess::Module;
+                    }
+                }
+            }
+        }
+        prev = curr;
+        prev_vis = curr_vis;
+        prev_attrs = curr_attrs;
+    }
+}
+
 fn insert_use_(
     scope: &ImportScope,
     insert_path: &ast::Path,
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
index 8189d6e53f..6a7a9b8f61 100644
--- a/crates/ide_db/src/helpers/merge_imports.rs
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -19,7 +19,6 @@ pub enum MergeBehavior {
 }
 
 impl MergeBehavior {
-    #[inline]
     fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
         match self {
             MergeBehavior::Crate => true,
@@ -114,7 +113,7 @@ fn recursive_merge(
                 let rhs_path = rhs_path?;
                 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
                 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
-                    let tree_is_self = |tree: ast::UseTree| {
+                    let tree_is_self = |tree: &ast::UseTree| {
                         tree.path().as_ref().map(path_is_self).unwrap_or(false)
                     };
                     // Check if only one of the two trees has a tree list, and
@@ -123,7 +122,7 @@ fn recursive_merge(
                     // the list is already included in the other one via `self`.
                     let tree_contains_self = |tree: &ast::UseTree| {
                         tree.use_tree_list()
-                            .map(|tree_list| tree_list.use_trees().any(tree_is_self))
+                            .map(|tree_list| tree_list.use_trees().any(|it| tree_is_self(&it)))
                             .unwrap_or(false)
                     };
                     match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
@@ -141,17 +140,18 @@ fn recursive_merge(
                     // glob import of said module see the `merge_self_glob` or
                     // `merge_mod_into_glob` tests.
                     if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
-                        *lhs_t = make::use_tree(
-                            make::path_unqualified(make::path_segment_self()),
-                            None,
-                            None,
-                            false,
-                        );
-                        use_trees.insert(idx, make::use_tree_glob());
-                        continue;
-                    }
-
-                    if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
+                        if tree_is_self(lhs_t) || tree_is_self(&rhs_t) {
+                            cov_mark::hit!(merge_self_glob);
+                            *lhs_t = make::use_tree(
+                                make::path_unqualified(make::path_segment_self()),
+                                None,
+                                None,
+                                false,
+                            );
+                            use_trees.insert(idx, make::use_tree_glob());
+                            continue;
+                        }
+                    } else if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
                         continue;
                     }
                 }
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs
index fcf1a654b0..0ca0dc8c6b 100644
--- a/crates/ide_db/src/rename.rs
+++ b/crates/ide_db/src/rename.rs
@@ -291,26 +291,23 @@ pub fn source_edit_from_references(
     new_name: &str,
 ) -> TextEdit {
     let mut edit = TextEdit::builder();
-    // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
-    let mut edited_ranges = Vec::new();
-    for &FileReference { range, ref name, .. } in references {
-        let has_emitted_edit = match name {
+    for reference in references {
+        let has_emitted_edit = match &reference.name {
             // if the ranges differ then the node is inside a macro call, we can't really attempt
             // to make special rewrites like shorthand syntax and such, so just rename the node in
             // the macro input
-            ast::NameLike::NameRef(name_ref) if name_ref.syntax().text_range() == range => {
+            ast::NameLike::NameRef(name_ref)
+                if name_ref.syntax().text_range() == reference.range =>
+            {
                 source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
             }
-            ast::NameLike::Name(name) if name.syntax().text_range() == range => {
+            ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
                 source_edit_from_name(&mut edit, name, new_name)
             }
             _ => false,
         };
         if !has_emitted_edit {
-            if !edited_ranges.contains(&range.start()) {
-                edit.replace(range, new_name.to_string());
-                edited_ranges.push(range.start());
-            }
+            edit.replace(reference.range, new_name.to_string());
         }
     }
 
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 06c21dfd70..7d8f97c775 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -63,7 +63,7 @@ use std::rc::Rc;
 
 use crate::{
     expander::{Binding, Bindings, Fragment},
-    parser::{Op, RepeatKind, Separator},
+    parser::{Op, OpDelimited, OpDelimitedIter, RepeatKind, Separator},
     tt_iter::TtIter,
     ExpandError, MetaTemplate,
 };
@@ -750,64 +750,6 @@ fn collect_vars(buf: &mut Vec<SmolStr>, pattern: &MetaTemplate) {
     }
 }
 
-impl MetaTemplate {
-    fn iter_delimited<'a>(&'a self, delimited: Option<&'a tt::Delimiter>) -> OpDelimitedIter<'a> {
-        OpDelimitedIter { inner: &self.0, idx: 0, delimited }
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-enum OpDelimited<'a> {
-    Op(&'a Op),
-    Open,
-    Close,
-}
-
-#[derive(Debug, Clone, Copy)]
-struct OpDelimitedIter<'a> {
-    inner: &'a [Op],
-    delimited: Option<&'a tt::Delimiter>,
-    idx: usize,
-}
-
-impl<'a> OpDelimitedIter<'a> {
-    fn is_eof(&self) -> bool {
-        let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 };
-        self.idx >= len
-    }
-
-    fn peek(&self) -> Option<OpDelimited<'a>> {
-        match self.delimited {
-            None => self.inner.get(self.idx).map(OpDelimited::Op),
-            Some(_) => match self.idx {
-                0 => Some(OpDelimited::Open),
-                i if i == self.inner.len() + 1 => Some(OpDelimited::Close),
-                i => self.inner.get(i - 1).map(OpDelimited::Op),
-            },
-        }
-    }
-
-    fn reset(&self) -> Self {
-        Self { inner: self.inner, idx: 0, delimited: self.delimited }
-    }
-}
-
-impl<'a> Iterator for OpDelimitedIter<'a> {
-    type Item = OpDelimited<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let res = self.peek();
-        self.idx += 1;
-        res
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 };
-        let remain = len.saturating_sub(self.idx);
-        (remain, Some(remain))
-    }
-}
-
 impl<'a> TtIter<'a> {
     fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool {
         let mut fork = self.clone();
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index 8470ea0aaf..0a1b7cc848 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -19,7 +19,7 @@ mod token_map;
 use std::fmt;
 
 use crate::{
-    parser::{MetaTemplate, Op},
+    parser::{parse_pattern, parse_template, MetaTemplate, Op},
     tt_iter::TtIter,
 };
 
@@ -275,8 +275,8 @@ impl Rule {
             .expect_subtree()
             .map_err(|()| ParseError::Expected("expected subtree".to_string()))?;
 
-        let lhs = MetaTemplate::parse_pattern(lhs)?;
-        let rhs = MetaTemplate::parse_template(rhs)?;
+        let lhs = MetaTemplate(parse_pattern(lhs)?);
+        let rhs = MetaTemplate(parse_template(rhs)?);
 
         Ok(crate::Rule { lhs, rhs })
     }
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 4ce818f9b0..deed884d2d 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -3,48 +3,75 @@
 
 use smallvec::SmallVec;
 use syntax::SmolStr;
+use tt::Delimiter;
 
 use crate::{tt_iter::TtIter, ParseError};
 
-/// Consider
-///
-/// ```
-/// macro_rules! an_macro {
-///     ($x:expr + $y:expr) => ($y * $x)
-/// }
-/// ```
-///
-/// Stuff to the left of `=>` is a [`MetaTemplate`] pattern (which is matched
-/// with input).
-///
-/// Stuff to the right is a [`MetaTemplate`] template which is used to produce
-/// output.
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) struct MetaTemplate(pub(crate) Vec<Op>);
 
-impl MetaTemplate {
-    pub(crate) fn parse_pattern(pattern: &tt::Subtree) -> Result<MetaTemplate, ParseError> {
-        MetaTemplate::parse(pattern, Mode::Pattern)
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum OpDelimited<'a> {
+    Op(&'a Op),
+    Open,
+    Close,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct OpDelimitedIter<'a> {
+    inner: &'a Vec<Op>,
+    delimited: Option<&'a Delimiter>,
+    idx: usize,
+}
+
+impl<'a> OpDelimitedIter<'a> {
+    pub(crate) fn is_eof(&self) -> bool {
+        let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 };
+        self.idx >= len
     }
 
-    pub(crate) fn parse_template(template: &tt::Subtree) -> Result<MetaTemplate, ParseError> {
-        MetaTemplate::parse(template, Mode::Template)
+    pub(crate) fn peek(&self) -> Option<OpDelimited<'a>> {
+        match self.delimited {
+            None => self.inner.get(self.idx).map(OpDelimited::Op),
+            Some(_) => match self.idx {
+                0 => Some(OpDelimited::Open),
+                i if i == self.inner.len() + 1 => Some(OpDelimited::Close),
+                i => self.inner.get(i - 1).map(OpDelimited::Op),
+            },
+        }
     }
 
-    pub(crate) fn iter(&self) -> impl Iterator<Item = &Op> {
-        self.0.iter()
+    pub(crate) fn reset(&self) -> Self {
+        Self { inner: self.inner, idx: 0, delimited: self.delimited }
     }
+}
 
-    fn parse(tt: &tt::Subtree, mode: Mode) -> Result<MetaTemplate, ParseError> {
-        let mut src = TtIter::new(tt);
+impl<'a> Iterator for OpDelimitedIter<'a> {
+    type Item = OpDelimited<'a>;
 
-        let mut res = Vec::new();
-        while let Some(first) = src.next() {
-            let op = next_op(first, &mut src, mode)?;
-            res.push(op)
-        }
+    fn next(&mut self) -> Option<Self::Item> {
+        let res = self.peek();
+        self.idx += 1;
+        res
+    }
 
-        Ok(MetaTemplate(res))
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 };
+        let remain = len.saturating_sub(self.idx);
+        (remain, Some(remain))
+    }
+}
+
+impl<'a> MetaTemplate {
+    pub(crate) fn iter(&self) -> impl Iterator<Item = &Op> {
+        self.0.iter()
+    }
+
+    pub(crate) fn iter_delimited(
+        &'a self,
+        delimited: Option<&'a Delimiter>,
+    ) -> OpDelimitedIter<'a> {
+        OpDelimitedIter { inner: &self.0, idx: 0, delimited }
     }
 }
 
@@ -53,7 +80,7 @@ pub(crate) enum Op {
     Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
     Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
     Leaf(tt::Leaf),
-    Subtree { tokens: MetaTemplate, delimiter: Option<tt::Delimiter> },
+    Subtree { tokens: MetaTemplate, delimiter: Option<Delimiter> },
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -98,12 +125,29 @@ impl Separator {
     }
 }
 
+pub(crate) fn parse_template(template: &tt::Subtree) -> Result<Vec<Op>, ParseError> {
+    parse_inner(template, Mode::Template).into_iter().collect()
+}
+
+pub(crate) fn parse_pattern(pattern: &tt::Subtree) -> Result<Vec<Op>, ParseError> {
+    parse_inner(pattern, Mode::Pattern).into_iter().collect()
+}
+
 #[derive(Clone, Copy)]
 enum Mode {
     Pattern,
     Template,
 }
 
+fn parse_inner(tt: &tt::Subtree, mode: Mode) -> Vec<Result<Op, ParseError>> {
+    let mut src = TtIter::new(tt);
+    std::iter::from_fn(move || {
+        let first = src.next()?;
+        Some(next_op(first, &mut src, mode))
+    })
+    .collect()
+}
+
 macro_rules! err {
     ($($tt:tt)*) => {
         ParseError::UnexpectedToken(($($tt)*).to_string())
@@ -127,8 +171,10 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
             match second {
                 tt::TokenTree::Subtree(subtree) => {
                     let (separator, kind) = parse_repeat(src)?;
-                    let tokens = MetaTemplate::parse(subtree, mode)?;
-                    Op::Repeat { tokens, separator, kind }
+                    let tokens = parse_inner(subtree, mode)
+                        .into_iter()
+                        .collect::<Result<Vec<Op>, ParseError>>()?;
+                    Op::Repeat { tokens: MetaTemplate(tokens), separator, kind }
                 }
                 tt::TokenTree::Leaf(leaf) => match leaf {
                     tt::Leaf::Punct(_) => {
@@ -159,8 +205,9 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
         }
         tt::TokenTree::Leaf(tt) => Op::Leaf(tt.clone()),
         tt::TokenTree::Subtree(subtree) => {
-            let tokens = MetaTemplate::parse(subtree, mode)?;
-            Op::Subtree { tokens, delimiter: subtree.delimiter }
+            let tokens =
+                parse_inner(subtree, mode).into_iter().collect::<Result<Vec<Op>, ParseError>>()?;
+            Op::Subtree { tokens: MetaTemplate(tokens), delimiter: subtree.delimiter }
         }
     };
     Ok(res)
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 40c0cd7a37..dc13e916c8 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -299,9 +299,6 @@ pub fn expr_return(expr: Option<ast::Expr>) -> ast::Expr {
 pub fn expr_try(expr: ast::Expr) -> ast::Expr {
     expr_from_text(&format!("{}?", expr))
 }
-pub fn expr_await(expr: ast::Expr) -> ast::Expr {
-    expr_from_text(&format!("{}.await", expr))
-}
 pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
     expr_from_text(&format!("match {} {}", expr, match_arm_list))
 }
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 800befb04b..a07f02e445 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -10,7 +10,10 @@ use parser::SyntaxKind;
 use rowan::{GreenNodeData, GreenTokenData};
 
 use crate::{
-    ast::{self, support, AstNode, AstToken, HasAttrs, HasGenericParams, HasName, SyntaxNode},
+    ast::{
+        self, support, AstNode, AstToken, HasAttrs, HasGenericParams, HasModuleItem, HasName,
+        SyntaxNode,
+    },
     NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
 };
 
@@ -51,6 +54,13 @@ impl ast::HasModuleItem for ast::StmtList {}
 
 impl ast::BlockExpr {
     // FIXME: remove all these methods, they belong to ast::StmtList
+    pub fn items(&self) -> impl Iterator<Item = ast::Item> {
+        self.stmt_list().into_iter().flat_map(|it| it.items())
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.statements().next().is_none() && self.tail_expr().is_none()
+    }
     pub fn statements(&self) -> impl Iterator<Item = ast::Stmt> {
         self.stmt_list().into_iter().flat_map(|it| it.statements())
     }
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 0b226a985d..3970f4ebb3 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -9,11 +9,11 @@
             "version": "0.4.0-dev",
             "license": "MIT OR Apache-2.0",
             "dependencies": {
-                "d3": "^7.1.0",
+                "d3": "^7.0.0",
                 "d3-graphviz": "^4.0.0",
                 "https-proxy-agent": "^5.0.0",
                 "node-fetch": "^2.6.1",
-                "vscode-languageclient": "8.0.0-next.2"
+                "vscode-languageclient": "^7.1.0-next.5"
             },
             "devDependencies": {
                 "@types/glob": "^7.1.4",
@@ -895,9 +895,9 @@
             }
         },
         "node_modules/d3": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.1.0.tgz",
-            "integrity": "sha512-gfc489DnwSwd6Bh+BMhR7lzfFj+Mw+HCXn4OFQwCVQFz6H8jbEt/vWqR5oClpj4uxehJsA7ecRKEVUiKHNL1bg==",
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.0.0.tgz",
+            "integrity": "sha512-t+jEKGO2jQiSBLJYYq6RFc500tsCeXBB4x41oQaSnZD3Som95nQrlw9XJGrFTMUOQOkwSMauWy9+8Tz1qm9UZw==",
             "dependencies": {
                 "d3-array": "3",
                 "d3-axis": "3",
@@ -3753,39 +3753,39 @@
             }
         },
         "node_modules/vscode-jsonrpc": {
-            "version": "8.0.0-next.2",
-            "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.2.tgz",
-            "integrity": "sha512-gxUyTBAjmwGkiHW/UaRScre2s4i98P8M7gnc3VB4DrVQUm3vQ0idi2cN9nbkfcjATx+uEt8C22j+MLN/8UzsJA==",
+            "version": "7.0.0-next.1",
+            "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-7.0.0-next.1.tgz",
+            "integrity": "sha512-dEmliPZGbSyIcEeKRGzosCy7y7zsc8FXg1l5BBOGgMUbemlo3vUnsa2GFqpILJwJvlbvkRcF2QASNwIlKe9J7g==",
             "engines": {
                 "node": ">=8.0.0 || >=10.0.0"
             }
         },
         "node_modules/vscode-languageclient": {
-            "version": "8.0.0-next.2",
-            "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.2.tgz",
-            "integrity": "sha512-ZmWnfZCwxV/FAci9jVMBnhwtdt9qkItXKqlmAU69pBSBgcr0fRrjEHOnzWG7BcuhxE0kti6/Y9bOEIn1L+OoDQ==",
+            "version": "7.1.0-next.5",
+            "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.1.0-next.5.tgz",
+            "integrity": "sha512-TpzpAhpdCNJHaLzptFRs54xsU6xTmaiVPCse0W0rRB5jJBPjOBKilrFPMMm/sJA0y8Yxa9sOvZaNu6WPg3dYAw==",
             "dependencies": {
                 "minimatch": "^3.0.4",
                 "semver": "^7.3.4",
-                "vscode-languageserver-protocol": "3.17.0-next.8"
+                "vscode-languageserver-protocol": "3.17.0-next.6"
             },
             "engines": {
-                "vscode": "^1.58.0"
+                "vscode": "^1.53.0"
             }
         },
         "node_modules/vscode-languageserver-protocol": {
-            "version": "3.17.0-next.8",
-            "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.8.tgz",
-            "integrity": "sha512-P89vSuJ+FA5JzFmcOoZN13Ig1yd6LsiPOig0O5m5BSGuO/rplQegCd9J0wKpaTy7trf/SYHRoypnbUBdzy14sg==",
+            "version": "3.17.0-next.6",
+            "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.6.tgz",
+            "integrity": "sha512-f1kGsoOpISB5jSqQNeMDl2446enxVahyux2e5vZap6pu/TC+2UlvPT4DCR0gPph95KOQZweL9zq1SzLoPdqhuA==",
             "dependencies": {
-                "vscode-jsonrpc": "8.0.0-next.2",
-                "vscode-languageserver-types": "3.17.0-next.3"
+                "vscode-jsonrpc": "7.0.0-next.1",
+                "vscode-languageserver-types": "3.17.0-next.2"
             }
         },
         "node_modules/vscode-languageserver-types": {
-            "version": "3.17.0-next.3",
-            "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.3.tgz",
-            "integrity": "sha512-VQcXnhKYxUW6OiRMhG++SzmZYMJwusXknJGd+FfdOnS1yHAo734OHyR0e2eEHDlv0/oWc8RZPgx/VKSKyondVg=="
+            "version": "3.17.0-next.2",
+            "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.2.tgz",
+            "integrity": "sha512-L5S2kNLCgYJMVWgsZjBaorMM/6+itAfvOyl6Kv1bgFzDNaUKm9HsnUlehjpWPdV5DqnfJhJ5E03Z+/3Mw8ii+Q=="
         },
         "node_modules/vscode-test": {
             "version": "1.5.2",
@@ -4645,9 +4645,9 @@
             "dev": true
         },
         "d3": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.1.0.tgz",
-            "integrity": "sha512-gfc489DnwSwd6Bh+BMhR7lzfFj+Mw+HCXn4OFQwCVQFz6H8jbEt/vWqR5oClpj4uxehJsA7ecRKEVUiKHNL1bg==",
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.0.0.tgz",
+            "integrity": "sha512-t+jEKGO2jQiSBLJYYq6RFc500tsCeXBB4x41oQaSnZD3Som95nQrlw9XJGrFTMUOQOkwSMauWy9+8Tz1qm9UZw==",
             "requires": {
                 "d3-array": "3",
                 "d3-axis": "3",
@@ -6818,33 +6818,33 @@
             }
         },
         "vscode-jsonrpc": {
-            "version": "8.0.0-next.2",
-            "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.2.tgz",
-            "integrity": "sha512-gxUyTBAjmwGkiHW/UaRScre2s4i98P8M7gnc3VB4DrVQUm3vQ0idi2cN9nbkfcjATx+uEt8C22j+MLN/8UzsJA=="
+            "version": "7.0.0-next.1",
+            "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-7.0.0-next.1.tgz",
+            "integrity": "sha512-dEmliPZGbSyIcEeKRGzosCy7y7zsc8FXg1l5BBOGgMUbemlo3vUnsa2GFqpILJwJvlbvkRcF2QASNwIlKe9J7g=="
         },
         "vscode-languageclient": {
-            "version": "8.0.0-next.2",
-            "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.2.tgz",
-            "integrity": "sha512-ZmWnfZCwxV/FAci9jVMBnhwtdt9qkItXKqlmAU69pBSBgcr0fRrjEHOnzWG7BcuhxE0kti6/Y9bOEIn1L+OoDQ==",
+            "version": "7.1.0-next.5",
+            "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.1.0-next.5.tgz",
+            "integrity": "sha512-TpzpAhpdCNJHaLzptFRs54xsU6xTmaiVPCse0W0rRB5jJBPjOBKilrFPMMm/sJA0y8Yxa9sOvZaNu6WPg3dYAw==",
             "requires": {
                 "minimatch": "^3.0.4",
                 "semver": "^7.3.4",
-                "vscode-languageserver-protocol": "3.17.0-next.8"
+                "vscode-languageserver-protocol": "3.17.0-next.6"
             }
         },
         "vscode-languageserver-protocol": {
-            "version": "3.17.0-next.8",
-            "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.8.tgz",
-            "integrity": "sha512-P89vSuJ+FA5JzFmcOoZN13Ig1yd6LsiPOig0O5m5BSGuO/rplQegCd9J0wKpaTy7trf/SYHRoypnbUBdzy14sg==",
+            "version": "3.17.0-next.6",
+            "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.6.tgz",
+            "integrity": "sha512-f1kGsoOpISB5jSqQNeMDl2446enxVahyux2e5vZap6pu/TC+2UlvPT4DCR0gPph95KOQZweL9zq1SzLoPdqhuA==",
             "requires": {
-                "vscode-jsonrpc": "8.0.0-next.2",
-                "vscode-languageserver-types": "3.17.0-next.3"
+                "vscode-jsonrpc": "7.0.0-next.1",
+                "vscode-languageserver-types": "3.17.0-next.2"
             }
         },
         "vscode-languageserver-types": {
-            "version": "3.17.0-next.3",
-            "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.3.tgz",
-            "integrity": "sha512-VQcXnhKYxUW6OiRMhG++SzmZYMJwusXknJGd+FfdOnS1yHAo734OHyR0e2eEHDlv0/oWc8RZPgx/VKSKyondVg=="
+            "version": "3.17.0-next.2",
+            "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.2.tgz",
+            "integrity": "sha512-L5S2kNLCgYJMVWgsZjBaorMM/6+itAfvOyl6Kv1bgFzDNaUKm9HsnUlehjpWPdV5DqnfJhJ5E03Z+/3Mw8ii+Q=="
         },
         "vscode-test": {
             "version": "1.5.2",
diff --git a/editors/code/package.json b/editors/code/package.json
index 5f38d1c126..a9593024d4 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -38,8 +38,8 @@
     "dependencies": {
         "https-proxy-agent": "^5.0.0",
         "node-fetch": "^2.6.1",
-        "vscode-languageclient": "8.0.0-next.2",
-        "d3": "^7.1.0",
+        "vscode-languageclient": "^7.1.0-next.5",
+        "d3": "^7.0.0",
         "d3-graphviz": "^4.0.0"
     },
     "devDependencies": {
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 726620578c..f69b7fdf56 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -66,7 +66,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
     ctx.pushCleanup(configureLanguage());
 
     vscode.workspace.onDidChangeConfiguration(
-        _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }).catch(log.error),
+        _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
         null,
         ctx.subscriptions,
     );


Test patch:
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 339360ba4a..f3b9c7130f 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -612,6 +612,7 @@ fn merge_mod_into_glob() {
 
 #[test]
 fn merge_self_glob() {
+    cov_mark::check!(merge_self_glob);
     check_with_config(
         "self",
         r"use self::*;",
@@ -627,6 +628,17 @@ fn merge_self_glob() {
     // FIXME: have it emit `use {self, *}`?
 }
 
+#[test]
+fn merge_glob() {
+    check_crate(
+        "syntax::SyntaxKind",
+        r"
+use syntax::{SyntaxKind::*};",
+        r"
+use syntax::{SyntaxKind::{*, self}};",
+    )
+}
+
 #[test]
 fn merge_glob_nested() {
     check_crate(
@@ -931,5 +943,5 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior
 fn check_guess(ra_fixture: &str, expected: ImportGranularityGuess) {
     let syntax = ast::SourceFile::parse(ra_fixture).tree().syntax().clone();
     let file = super::ImportScope::from(syntax).unwrap();
-    assert_eq!(file.guess_granularity_from_scope(), expected);
+    assert_eq!(super::guess_granularity_from_scope(&file), expected);
 }