Repository: rust-analyzer/rust-analyzer (rust)

Issue:
"called `Option::unwrap()` on a `None` value" in src/ast/make.rs:303:16
I'm debugging the hang/crash I saw while hacking on rustc's MIR transforms with rust-analyzer enabled, and this backtrace shows up quite a few times in the output (I don't think it's what causes the hang/crash though):

```
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', crates/ra_syntax/src/ast/make.rs:303:16
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.44/src/backtrace/libunwind.rs:86
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.44/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:78
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src/libstd/sys_common/backtrace.rs:59
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1063
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1426
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:62
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:49
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:204
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:224
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:470
  11: rust_begin_unwind
             at src/libstd/panicking.rs:378
  12: core::panicking::panic_fmt
             at src/libcore/panicking.rs:85
  13: core::panicking::panic
             at src/libcore/panicking.rs:52
  14: ra_syntax::ast::make::path_from_text
  15: ra_syntax::ast::make::path_unqualified
  16: ra_syntax::ast::edit::<impl ra_syntax::ast::generated::nodes::UseTree>::split_prefix::split_path_prefix
  17: ra_syntax::ast::edit::<impl ra_syntax::ast::generated::nodes::UseTree>::split_prefix
  18: ra_assists::handlers::merge_imports::try_merge_trees
  19: <core::iter::adapters::Copied<I> as core::iter::traits::iterator::Iterator>::try_fold
  20: ra_assists::handlers::merge_imports::merge_imports
  21: <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::next
  22: ra_assists::resolved_assists
  23: std::panicking::try::do_call
  24: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:86
  25: ra_ide::Analysis::assists
  26: rust_analyzer::main_loop::handlers::handle_code_action
  27: <F as threadpool::FnBox>::call_box
```

I did not try to use the merge imports assist here.

Pull request:
Fix panic in split/merge import assists
Fixes #4368 #4905

Not sure if this is the best solution here. Maybe the `make` functions should be fallible? We generally seem to be playing whack-a-mole with panics in assists, although most of them are `unwrap`s in the assist code.

Patch:
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index 972d162419..ac0b3035c8 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -127,7 +127,7 @@ fn first_path(path: &ast::Path) -> ast::Path {
 
 #[cfg(test)]
 mod tests {
-    use crate::tests::check_assist;
+    use crate::tests::{check_assist, check_assist_not_applicable};
 
     use super::*;
 
@@ -276,4 +276,14 @@ bar::baz};
 ",
         )
     }
+
+    #[test]
+    fn test_empty_use() {
+        check_assist_not_applicable(
+            merge_imports,
+            r"
+use std::<|>
+fn main() {}",
+        );
+    }
 }
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index c7a8744802..38aa199a06 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -66,4 +66,14 @@ mod tests {
     fn issue4044() {
         check_assist_not_applicable(split_import, "use crate::<|>:::self;")
     }
+
+    #[test]
+    fn test_empty_use() {
+        check_assist_not_applicable(
+            split_import,
+            r"
+use std::<|>
+fn main() {}",
+        );
+    }
 }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 03f18c617f..59c230f6cd 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -9,7 +9,6 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
 // Search and replace with named wildcards that will match any expression, type, path, pattern or item.
 // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
 // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
-// Within a macro call, a placeholder will match up until whatever token follows the placeholder.
 // Available via the command `rust-analyzer.ssr`.
 //
 // ```rust
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
index 9d82b40095..415f24a6d3 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -155,21 +155,17 @@ pub(super) fn highlight_doc_comment(
         let mut start_offset = None;
         let mut end_offset = None;
         for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
-            // It's possible for orig_line_start - line_start to be negative. Add h.range.start()
-            // here and remove it from the end range after the loop below so that the values are
-            // always non-negative.
-            let offset = h.range.start() + orig_line_start - line_start;
             if line_start <= &h.range.start() {
-                start_offset.get_or_insert(offset);
+                start_offset.get_or_insert(orig_line_start - line_start);
                 break;
             } else {
-                end_offset.get_or_insert(offset);
+                end_offset.get_or_insert(orig_line_start - line_start);
             }
         }
         if let Some(start_offset) = start_offset {
             h.range = TextRange::new(
-                start_offset,
-                h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(),
+                h.range.start() + start_offset,
+                h.range.end() + end_offset.unwrap_or(start_offset),
             );
 
             stack.add(h);
diff --git a/crates/ra_parser/src/grammar/paths.rs b/crates/ra_parser/src/grammar/paths.rs
index 428aa711e1..fd51189d59 100644
--- a/crates/ra_parser/src/grammar/paths.rs
+++ b/crates/ra_parser/src/grammar/paths.rs
@@ -73,8 +73,10 @@ fn path_segment(p: &mut Parser, mode: Mode, first: bool) {
         }
         p.expect(T![>]);
     } else {
+        let mut empty = true;
         if first {
             p.eat(T![::]);
+            empty = false;
         }
         match p.current() {
             IDENT => {
@@ -86,6 +88,12 @@ fn path_segment(p: &mut Parser, mode: Mode, first: bool) {
             T![self] | T![super] | T![crate] => p.bump_any(),
             _ => {
                 p.err_recover("expected identifier", items::ITEM_RECOVERY_SET);
+                if empty {
+                    // test_err empty_segment
+                    // use crate::;
+                    m.abandon(p);
+                    return;
+                }
             }
         };
     }
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index da26ee6694..fc716ae829 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -91,16 +91,14 @@ impl<'db> MatchFinder<'db> {
             if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) {
                 // Continue searching in each of our placeholders.
                 for placeholder_value in m.placeholder_values.values_mut() {
-                    if let Some(placeholder_node) = &placeholder_value.node {
-                        // Don't search our placeholder if it's the entire matched node, otherwise we'd
-                        // find the same match over and over until we got a stack overflow.
-                        if placeholder_node != code {
-                            self.find_matches(
-                                placeholder_node,
-                                restrict_range,
-                                &mut placeholder_value.inner_matches,
-                            );
-                        }
+                    // Don't search our placeholder if it's the entire matched node, otherwise we'd
+                    // find the same match over and over until we got a stack overflow.
+                    if placeholder_value.node != *code {
+                        self.find_matches(
+                            &placeholder_value.node,
+                            restrict_range,
+                            &mut placeholder_value.inner_matches,
+                        );
                     }
                 }
                 matches_out.matches.push(m);
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index bdaba9f1b6..265b6d793e 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -61,9 +61,8 @@ pub(crate) struct Var(pub String);
 /// Information about a placeholder bound in a match.
 #[derive(Debug)]
 pub(crate) struct PlaceholderMatch {
-    /// The node that the placeholder matched to. If set, then we'll search for further matches
-    /// within this node. It isn't set when we match tokens within a macro call's token tree.
-    pub(crate) node: Option<SyntaxNode>,
+    /// The node that the placeholder matched to.
+    pub(crate) node: SyntaxNode,
     pub(crate) range: FileRange,
     /// More matches, found within `node`.
     pub(crate) inner_matches: SsrMatches,
@@ -196,7 +195,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
             SyntaxKind::RECORD_FIELD_LIST => {
                 self.attempt_match_record_field_list(match_inputs, pattern, code)
             }
-            SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code),
             _ => self.attempt_match_node_children(match_inputs, pattern, code),
         }
     }
@@ -342,90 +340,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
         Ok(())
     }
 
-    /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
-    /// tree it can match a sequence of tokens.
-    fn attempt_match_token_tree(
-        &mut self,
-        match_inputs: &MatchInputs,
-        pattern: &SyntaxNode,
-        code: &ra_syntax::SyntaxNode,
-    ) -> Result<(), MatchFailed> {
-        let mut pattern = PatternIterator::new(pattern).peekable();
-        let mut children = code.children_with_tokens();
-        while let Some(child) = children.next() {
-            if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p))
-            {
-                pattern.next();
-                let next_pattern_token = pattern
-                    .peek()
-                    .and_then(|p| match p {
-                        SyntaxElement::Token(t) => Some(t.clone()),
-                        SyntaxElement::Node(n) => n.first_token(),
-                    })
-                    .map(|p| p.text().to_string());
-                let first_matched_token = child.clone();
-                let mut last_matched_token = child;
-                // Read code tokens util we reach one equal to the next token from our pattern
-                // or we reach the end of the token tree.
-                while let Some(next) = children.next() {
-                    match &next {
-                        SyntaxElement::Token(t) => {
-                            if Some(t.to_string()) == next_pattern_token {
-                                pattern.next();
-                                break;
-                            }
-                        }
-                        SyntaxElement::Node(n) => {
-                            if let Some(first_token) = n.first_token() {
-                                if Some(first_token.to_string()) == next_pattern_token {
-                                    if let Some(SyntaxElement::Node(p)) = pattern.next() {
-                                        // We have a subtree that starts with the next token in our pattern.
-                                        self.attempt_match_token_tree(match_inputs, &p, &n)?;
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    };
-                    last_matched_token = next;
-                }
-                if let Some(match_out) = &mut self.match_out {
-                    match_out.placeholder_values.insert(
-                        Var(placeholder.ident.to_string()),
-                        PlaceholderMatch::from_range(FileRange {
-                            file_id: self.sema.original_range(code).file_id,
-                            range: first_matched_token
-                                .text_range()
-                                .cover(last_matched_token.text_range()),
-                        }),
-                    );
-                }
-                continue;
-            }
-            // Match literal (non-placeholder) tokens.
-            match child {
-                SyntaxElement::Token(token) => {
-                    self.attempt_match_token(&mut pattern, &token)?;
-                }
-                SyntaxElement::Node(node) => match pattern.next() {
-                    Some(SyntaxElement::Node(p)) => {
-                        self.attempt_match_token_tree(match_inputs, &p, &node)?;
-                    }
-                    Some(SyntaxElement::Token(p)) => fail_match!(
-                        "Pattern has token '{}', code has subtree '{}'",
-                        p.text(),
-                        node.text()
-                    ),
-                    None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
-                },
-            }
-        }
-        if let Some(p) = pattern.next() {
-            fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
-        }
-        Ok(())
-    }
-
     fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
         loop {
             let c = code_it.next();
@@ -485,11 +399,7 @@ fn recording_match_fail_reasons() -> bool {
 
 impl PlaceholderMatch {
     fn new(node: &SyntaxNode, range: FileRange) -> Self {
-        Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() }
-    }
-
-    fn from_range(range: FileRange) -> Self {
-        Self { node: None, range, inner_matches: SsrMatches::default() }
+        Self { node: node.clone(), range, inner_matches: SsrMatches::default() }
     }
 }
 
@@ -574,14 +484,7 @@ mod tests {
         assert_eq!(matches.matches.len(), 1);
         assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
         assert_eq!(matches.matches[0].placeholder_values.len(), 1);
-        assert_eq!(
-            matches.matches[0].placeholder_values[&Var("x".to_string())]
-                .node
-                .as_ref()
-                .unwrap()
-                .text(),
-            "1+2"
-        );
+        assert_eq!(matches.matches[0].placeholder_values[&Var("x".to_string())].node.text(), "1+2");
 
         let edit = crate::replacing::matches_to_edit(&matches);
         let mut after = input.to_string();
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs
index 5dcde82a28..81a5e06a9c 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -24,7 +24,6 @@ fn matches_to_edit_at_offset(matches: &SsrMatches, relative_start: TextSize) ->
 
 fn render_replace(match_info: &Match) -> String {
     let mut out = String::new();
-    let match_start = match_info.matched_node.text_range().start();
     for r in &match_info.template.tokens {
         match r {
             PatternElement::Token(t) => out.push_str(t.text.as_str()),
@@ -33,14 +32,7 @@ fn render_replace(match_info: &Match) -> String {
                     match_info.placeholder_values.get(&Var(p.ident.to_string()))
                 {
                     let range = &placeholder_value.range.range;
-                    let mut matched_text = if let Some(node) = &placeholder_value.node {
-                        node.text().to_string()
-                    } else {
-                        let relative_range = range.checked_sub(match_start).unwrap();
-                        match_info.matched_node.text().to_string()
-                            [usize::from(relative_range.start())..usize::from(relative_range.end())]
-                            .to_string()
-                    };
+                    let mut matched_text = placeholder_value.node.text().to_string();
                     let edit =
                         matches_to_edit_at_offset(&placeholder_value.inner_matches, range.start());
                     edit.apply(&mut matched_text);
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 1b63d82236..76e1da6cf4 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -348,6 +348,8 @@ To update test data, run with `UPDATE_EXPECTATIONS` variable:
 env UPDATE_EXPECTATIONS=1 cargo qt
 ```
 
+After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above.
+
 # Logging
 
 Logging is done by both rust-analyzer and VS Code, so it might be tricky to
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index f1b7ed7fc8..ea714f49ad 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -269,10 +269,6 @@ Gnome Builder currently has support for RLS, and there's no way to configure the
 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
 2. Enable the Rust Builder plugin.
 
-==== GNOME Builder (Nightly)
-
-https://nightly.gnome.org/repo/appstream/org.gnome.Builder.flatpakref[GNOME Builder (Nightly)] has now native support for `rust-analyzer` out of the box. If the `rust-analyzer` binary is not available, GNOME Builder can install it when opening a Rust source file.
-
 == Non-Cargo Based Projects
 
 rust-analyzer does not require Cargo.
diff --git a/editors/code/package.json b/editors/code/package.json
index 68484a370b..e6ceb235fc 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -426,7 +426,7 @@
                         "Full log"
                     ],
                     "default": "off",
-                    "description": "Trace requests to the rust-analyzer (this is usually overly verbose and not recommended for regular users)"
+                    "description": "Trace requests to the rust-analyzer"
                 },
                 "rust-analyzer.trace.extension": {
                     "description": "Enable logging of VS Code extensions itself",
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 12b4d05108..670f2ebfd6 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -43,16 +43,12 @@ export async function activate(context: vscode.ExtensionContext) {
     const config = new Config(context);
     const state = new PersistentState(context.globalState);
     const serverPath = await bootstrap(config, state).catch(err => {
-        let message = "bootstrap error. ";
-
+        let message = "Failed to bootstrap rust-analyzer.";
         if (err.code === "EBUSY" || err.code === "ETXTBSY") {
-            message += "Other vscode windows might be using rust-analyzer, ";
-            message += "you should close them and reload this window to retry. ";
+            message += " Other vscode windows might be using rust-analyzer, " +
+                "you should close them and reload this window to retry.";
         }
-
-        message += 'Open "Help > Toggle Developer Tools > Console" to see the logs ';
-        message += '(enable verbose logs with "rust-analyzer.trace.extension")';
-
+        message += " Open \"Help > Toggle Developer Tools > Console\" to see the logs";
         log.error("Bootstrap error", err);
         throw new Error(message);
     });
@@ -182,11 +178,7 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
     assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
 
     const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix");
-    await download({
-        url: artifact.browser_download_url,
-        dest,
-        progressTitle: "Downloading rust-analyzer extension",
-    });
+    await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension");
 
     await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest));
     await fs.unlink(dest);
@@ -307,12 +299,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
         if (err.code !== "ENOENT") throw err;
     });
 
-    await download({
-        url: artifact.browser_download_url,
-        dest,
-        progressTitle: "Downloading rust-analyzer server",
-        mode: 0o755
-    });
+    await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
 
     // Patching executable if that's NixOS.
     if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) {
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index e02fd6d4f4..9debdc57bf 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -60,40 +60,32 @@ export interface GithubRelease {
     }>;
 }
 
-interface DownloadOpts {
-    progressTitle: string;
-    url: string;
-    dest: string;
-    mode?: number;
-}
 
-export async function download(opts: DownloadOpts) {
-    // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode
-    await withTempDir(async tempDir => {
-        const tempFile = path.join(tempDir, path.basename(opts.dest));
-
-        await vscode.window.withProgress(
-            {
-                location: vscode.ProgressLocation.Notification,
-                cancellable: false,
-                title: opts.progressTitle
-            },
-            async (progress, _cancellationToken) => {
-                let lastPercentage = 0;
-                await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => {
-                    const newPercentage = (readBytes / totalBytes) * 100;
-                    progress.report({
-                        message: newPercentage.toFixed(0) + "%",
-                        increment: newPercentage - lastPercentage
-                    });
-
-                    lastPercentage = newPercentage;
+export async function download(
+    downloadUrl: string,
+    destinationPath: string,
+    progressTitle: string,
+    { mode }: { mode?: number } = {},
+) {
+    await vscode.window.withProgress(
+        {
+            location: vscode.ProgressLocation.Notification,
+            cancellable: false,
+            title: progressTitle
+        },
+        async (progress, _cancellationToken) => {
+            let lastPercentage = 0;
+            await downloadFile(downloadUrl, destinationPath, mode, (readBytes, totalBytes) => {
+                const newPercentage = (readBytes / totalBytes) * 100;
+                progress.report({
+                    message: newPercentage.toFixed(0) + "%",
+                    increment: newPercentage - lastPercentage
                 });
-            }
-        );
 
-        await moveFile(tempFile, opts.dest);
-    });
+                lastPercentage = newPercentage;
+            });
+        }
+    );
 }
 
 /**
@@ -122,23 +114,28 @@ async function downloadFile(
 
     log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath);
 
-    let readBytes = 0;
-    res.body.on("data", (chunk: Buffer) => {
-        readBytes += chunk.length;
-        onProgress(readBytes, totalBytes);
-    });
+    // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode
+    await withTempFile(async tempFilePath => {
+        const destFileStream = fs.createWriteStream(tempFilePath, { mode });
 
-    const destFileStream = fs.createWriteStream(destFilePath, { mode });
-    await pipeline(res.body, destFileStream);
-    await new Promise<void>(resolve => {
-        destFileStream.on("close", resolve);
-        destFileStream.destroy();
-        // This workaround is awaiting to be removed when vscode moves to newer nodejs version:
-        // https://github.com/rust-analyzer/rust-analyzer/issues/3167
+        let readBytes = 0;
+        res.body.on("data", (chunk: Buffer) => {
+            readBytes += chunk.length;
+            onProgress(readBytes, totalBytes);
+        });
+
+        await pipeline(res.body, destFileStream);
+        await new Promise<void>(resolve => {
+            destFileStream.on("close", resolve);
+            destFileStream.destroy();
+            // This workaround is awaiting to be removed when vscode moves to newer nodejs version:
+            // https://github.com/rust-analyzer/rust-analyzer/issues/3167
+        });
+        await moveFile(tempFilePath, destFilePath);
     });
 }
 
-async function withTempDir(scope: (tempDirPath: string) => Promise<void>) {
+async function withTempFile(scope: (tempFilePath: string) => Promise<void>) {
     // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/
 
     // `.realpath()` should handle the cases where os.tmpdir() contains symlinks
@@ -147,7 +144,7 @@ async function withTempDir(scope: (tempDirPath: string) => Promise<void>) {
     const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer"));
 
     try {
-        return await scope(tempDir);
+        return await scope(path.join(tempDir, "file"));
     } finally {
         // We are good citizens :D
         void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error);
@@ -164,7 +161,6 @@ async function moveFile(src: fs.PathLike, dest: fs.PathLike) {
             await fs.promises.unlink(src);
         } else {
             log.error(`Failed to rename the file ${src} -> ${dest}`, err);
-            throw err;
         }
     }
 }
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index fec4c3295e..fe3fb71cd7 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -26,6 +26,7 @@ export const log = new class {
     }
 
     error(message?: any, ...optionalParams: any[]): void {
+        if (!log.enabled) return;
         debugger;
         // eslint-disable-next-line no-console
         console.error(message, ...optionalParams);


Test patch:
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
index ac546806eb..63199cdbe2 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -32,10 +32,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
 .control            { font-style: italic; }
 </style>
-<pre><code><span class="comment documentation">/// ```</span>
-<span class="comment documentation">/// </span><span class="keyword">let</span> _ = <span class="string_literal">"early doctests should not go boom"</span>;
-<span class="comment documentation">/// ```</span>
-<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
+<pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
     <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
 }
 
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index b1f48f03bc..93a276ffe1 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -291,9 +291,6 @@ fn main() {
 fn test_highlight_doctest() {
     check_highlighting(
         r#"
-/// ```
-/// let _ = "early doctests should not go boom";
-/// ```
 struct Foo {
     bar: bool,
 }
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index 3ee1e74e95..4b747fe180 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -426,45 +426,6 @@ fn match_reordered_struct_instantiation() {
     assert_no_match("Foo {a: 1, z: 9}", "fn f() {Foo {a: 1}}");
 }
 
-#[test]
-fn match_macro_invocation() {
-    assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]);
-    assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]);
-    assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}");
-    assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}");
-    assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]);
-}
-
-// When matching within a macro expansion, we only allow matches of nodes that originated from
-// the macro call, not from the macro definition.
-#[test]
-fn no_match_expression_from_macro() {
-    assert_no_match(
-        "$a.clone()",
-        r#"
-            macro_rules! m1 {
-                () => {42.clone()}
-            }
-            fn f1() {m1!()}
-            "#,
-    );
-}
-
-// We definitely don't want to allow matching of an expression that part originates from the
-// macro call `42` and part from the macro definition `.clone()`.
-#[test]
-fn no_match_split_expression() {
-    assert_no_match(
-        "$a.clone()",
-        r#"
-            macro_rules! m1 {
-                ($x:expr) => {$x.clone()}
-            }
-            fn f1() {m1!(42)}
-            "#,
-    );
-}
-
 #[test]
 fn replace_function_call() {
     assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}");
@@ -506,20 +467,6 @@ fn replace_struct_init() {
     );
 }
 
-#[test]
-fn replace_macro_invocations() {
-    assert_ssr_transform(
-        "try!($a) ==>> $a?",
-        "fn f1() -> Result<(), E> {bar(try!(foo()));}",
-        "fn f1() -> Result<(), E> {bar(foo()?);}",
-    );
-    assert_ssr_transform(
-        "foo!($a($b)) ==>> foo($b, $a)",
-        "fn f1() {foo!(abc(def() + 2));}",
-        "fn f1() {foo(def() + 2, abc);}",
-    );
-}
-
 #[test]
 fn replace_binary_op() {
     assert_ssr_transform(
diff --git a/crates/ra_syntax/test_data/parser/err/0004_use_path_bad_segment.rast b/crates/ra_syntax/test_data/parser/err/0004_use_path_bad_segment.rast
index 8c6b89dc25..b3bcf472a2 100644
--- a/crates/ra_syntax/test_data/parser/err/0004_use_path_bad_segment.rast
+++ b/crates/ra_syntax/test_data/parser/err/0004_use_path_bad_segment.rast
@@ -9,8 +9,7 @@ SOURCE_FILE@0..12
             NAME_REF@4..7
               IDENT@4..7 "foo"
         COLON2@7..9 "::"
-        PATH_SEGMENT@9..11
-          ERROR@9..11
-            INT_NUMBER@9..11 "92"
+        ERROR@9..11
+          INT_NUMBER@9..11 "92"
     SEMICOLON@11..12 ";"
 error 9..9: expected identifier
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rast b/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rast
new file mode 100644
index 0000000000..da8505607e
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rast
@@ -0,0 +1,15 @@
+SOURCE_FILE@0..13
+  USE_ITEM@0..12
+    USE_KW@0..3 "use"
+    WHITESPACE@3..4 " "
+    USE_TREE@4..12
+      PATH@4..12
+        PATH@4..9
+          PATH_SEGMENT@4..9
+            CRATE_KW@4..9 "crate"
+        COLON2@9..11 "::"
+        ERROR@11..12
+          SEMICOLON@11..12 ";"
+  WHITESPACE@12..13 "\n"
+error 11..11: expected identifier
+error 12..12: expected SEMICOLON
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rs b/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rs
new file mode 100644
index 0000000000..7510664e10
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rs
@@ -0,0 +1,1 @@
+use crate::;