Repository: rust-analyzer/rust-analyzer
Pull request #5513 (2020-07-24 03:27:02)

Try figure out correct workspace in vscode multi root workspace
the code to replace the root with the `${workspaceRoot}` arg breaks in multi root workspaces as it needs a qualifier `${workspaceRoot:workspaceName}`

This PR attempts to figure out the root workspace - and if it cant find it falls back to the first workspace

matklad: @vsrs you probably are more qualified to review this :)
vsrs: @urbandove 

The only reason why the simplification exists is to have better `launch.json` when someone saves a debug configuration (via `rust-analyzer.newDebugConfig` command for example).

With:
```jsonc
{
// ...
    "cwd": "${workspaceRoot}"
}
```

Without:
```
{
// ...
    "cwd": "E:\Projects\Rust\RA\test_projects\multiple_binaries\"
}
```
And it's not good to have the absolute path in the `launch.json`.

I've never worked with multi-root workspaces, but I think it should be possible to modify `simplifyPath` function and use `${workspaceRoot:workspaceName}` for a multi-root workspace.

Patch:
diff --git a/.github/workflows/metrics.yaml b/.github/workflows/metrics.yaml
deleted file mode 100644
index ea780e8756..0000000000
--- a/.github/workflows/metrics.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: metrics
-on:
-  push:
-   branches:
-   - master
-
-env:
-  CARGO_INCREMENTAL: 0
-  CARGO_NET_RETRY: 10
-  RUSTFLAGS: -D warnings
-  RUSTUP_MAX_RETRIES: 10
-
-jobs:
-  metrics:
-    runs-on: ubuntu-latest
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-
-    - name: Install Rust toolchain
-      uses: actions-rs/toolchain@v1
-      with:
-        toolchain: stable
-        profile: minimal
-        override: true
-        components: rust-src
-
-    - name: Collect metrics
-      run: cargo xtask metrics
-      env:
-        METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }}
diff --git a/Cargo.lock b/Cargo.lock
index 8bed2b1afc..0a35eb7932 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1164,7 +1164,6 @@ dependencies = [
 name = "ra_ssr"
 version = "0.1.0"
 dependencies = [
- "expect",
  "ra_db",
  "ra_hir",
  "ra_ide_db",
diff --git a/README.md b/README.md
index 16c980400c..7ba705e737 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,11 @@
   <img src="https://user-images.githubusercontent.com/1711539/72443316-5a79f280-37ae-11ea-858f-035209ece2dd.png" alt="rust-analyzer logo">
 </p>
 
-rust-analyzer is an **experimental** modular compiler frontend for the Rust language.
-It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
+rust-analyzer is an **experimental** modular compiler frontend for the Rust
+language. It is a part of a larger rls-2.0 effort to create excellent IDE
+support for Rust. If you want to get involved, check the rls-2.0 working group:
+
+https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
 
 Work on rust-analyzer is sponsored by
 
@@ -22,8 +25,8 @@ If you want to **contribute** to rust-analyzer or are just curious about how
 things work under the hood, check the [./docs/dev](./docs/dev) folder.
 
 If you want to **use** rust-analyzer's language server with your editor of
-choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder.
-It also contains some tips & tricks to help you be more productive when using rust-analyzer.
+choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder. It also contains some tips & tricks to help
+you be more productive when using rust-analyzer.
 
 ## Communication
 
@@ -37,9 +40,8 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
 
 ## Quick Links
 
-* Website: https://rust-analyzer.github.io/
-* Metrics: https://rust-analyzer.github.io/metrics/
 * API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide/
+* Website: https://rust-analyzer.github.io/
 
 ## License
 
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
index 266b513dcf..11a0ecb8b2 100644
--- a/crates/ra_hir/src/diagnostics.rs
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -1,8 +1,6 @@
 //! FIXME: write short doc here
 pub use hir_def::diagnostics::UnresolvedModule;
-pub use hir_expand::diagnostics::{
-    AstDiagnostic, Diagnostic, DiagnosticSink, DiagnosticSinkBuilder,
-};
+pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
 pub use hir_ty::diagnostics::{
     MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
 };
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 1436b1afee..f5283ab220 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -89,7 +89,6 @@ pub struct Semantics<'db, DB> {
 pub struct SemanticsImpl<'db> {
     pub db: &'db dyn HirDatabase,
     s2d_cache: RefCell<SourceToDefCache>,
-    expansion_info_cache: RefCell<FxHashMap<HirFileId, Option<ExpansionInfo>>>,
     cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
 }
 
@@ -276,12 +275,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
 
 impl<'db> SemanticsImpl<'db> {
     fn new(db: &'db dyn HirDatabase) -> Self {
-        SemanticsImpl {
-            db,
-            s2d_cache: Default::default(),
-            cache: Default::default(),
-            expansion_info_cache: Default::default(),
-        }
+        Self { db, s2d_cache: Default::default(), cache: Default::default() }
     }
 
     fn parse(&self, file_id: FileId) -> ast::SourceFile {
@@ -321,26 +315,18 @@ impl<'db> SemanticsImpl<'db> {
     }
 
     fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
-        let _p = profile("descend_into_macros");
         let parent = token.parent();
         let parent = self.find_file(parent);
         let sa = self.analyze2(parent.as_ref(), None);
 
         let token = successors(Some(parent.with_value(token)), |token| {
-            self.db.check_canceled();
             let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
             let tt = macro_call.token_tree()?;
             if !tt.syntax().text_range().contains_range(token.value.text_range()) {
                 return None;
             }
             let file_id = sa.expand(self.db, token.with_value(&macro_call))?;
-            let token = self
-                .expansion_info_cache
-                .borrow_mut()
-                .entry(file_id)
-                .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
-                .as_ref()?
-                .map_token_down(token.as_ref())?;
+            let token = file_id.expansion_info(self.db.upcast())?.map_token_down(token.as_ref())?;
 
             self.cache(find_root(&token.value.parent()), token.file_id);
 
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs
index 84ba97b14a..545cff9bd1 100644
--- a/crates/ra_hir_expand/src/diagnostics.rs
+++ b/crates/ra_hir_expand/src/diagnostics.rs
@@ -24,9 +24,6 @@ pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
     fn message(&self) -> String;
     fn source(&self) -> InFile<SyntaxNodePtr>;
     fn as_any(&self) -> &(dyn Any + Send + 'static);
-    fn is_experimental(&self) -> bool {
-        false
-    }
 }
 
 pub trait AstDiagnostic {
@@ -47,48 +44,16 @@ impl dyn Diagnostic {
 
 pub struct DiagnosticSink<'a> {
     callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>,
-    filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>,
     default_callback: Box<dyn FnMut(&dyn Diagnostic) + 'a>,
 }
 
 impl<'a> DiagnosticSink<'a> {
-    pub fn push(&mut self, d: impl Diagnostic) {
-        let d: &dyn Diagnostic = &d;
-        self._push(d);
-    }
-
-    fn _push(&mut self, d: &dyn Diagnostic) {
-        for filter in &mut self.filters {
-            if !filter(d) {
-                return;
-            }
-        }
-        for cb in &mut self.callbacks {
-            match cb(d) {
-                Ok(()) => return,
-                Err(()) => (),
-            }
-        }
-        (self.default_callback)(d)
+    /// FIXME: split `new` and `on` into a separate builder type
+    pub fn new(cb: impl FnMut(&dyn Diagnostic) + 'a) -> DiagnosticSink<'a> {
+        DiagnosticSink { callbacks: Vec::new(), default_callback: Box::new(cb) }
     }
-}
 
-pub struct DiagnosticSinkBuilder<'a> {
-    callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>,
-    filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>,
-}
-
-impl<'a> DiagnosticSinkBuilder<'a> {
-    pub fn new() -> Self {
-        Self { callbacks: Vec::new(), filters: Vec::new() }
-    }
-
-    pub fn filter<F: FnMut(&dyn Diagnostic) -> bool + 'a>(mut self, cb: F) -> Self {
-        self.filters.push(Box::new(cb));
-        self
-    }
-
-    pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self {
+    pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> DiagnosticSink<'a> {
         let cb = move |diag: &dyn Diagnostic| match diag.downcast_ref::<D>() {
             Some(d) => {
                 cb(d);
@@ -100,11 +65,18 @@ impl<'a> DiagnosticSinkBuilder<'a> {
         self
     }
 
-    pub fn build<F: FnMut(&dyn Diagnostic) + 'a>(self, default_callback: F) -> DiagnosticSink<'a> {
-        DiagnosticSink {
-            callbacks: self.callbacks,
-            filters: self.filters,
-            default_callback: Box::new(default_callback),
+    pub fn push(&mut self, d: impl Diagnostic) {
+        let d: &dyn Diagnostic = &d;
+        self._push(d);
+    }
+
+    fn _push(&mut self, d: &dyn Diagnostic) {
+        for cb in self.callbacks.iter_mut() {
+            match cb(d) {
+                Ok(()) => return,
+                Err(()) => (),
+            }
         }
+        (self.default_callback)(d)
     }
 }
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 7242e2cb6d..cdabb359f2 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -37,4 +37,4 @@ expect = { path = "../expect" }
 
 tracing = "0.1"
 tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
-tracing-tree = { version = "0.1.4" }
+tracing-tree = { version = "0.1.3" }
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 885abbaf2d..d3ee9cf556 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -234,9 +234,6 @@ impl Diagnostic for MismatchedArgCount {
     fn as_any(&self) -> &(dyn Any + Send + 'static) {
         self
     }
-    fn is_experimental(&self) -> bool {
-        true
-    }
 }
 
 impl AstDiagnostic for MismatchedArgCount {
@@ -251,7 +248,7 @@ impl AstDiagnostic for MismatchedArgCount {
 #[cfg(test)]
 mod tests {
     use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
-    use hir_expand::diagnostics::{Diagnostic, DiagnosticSinkBuilder};
+    use hir_expand::diagnostics::{Diagnostic, DiagnosticSink};
     use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
     use ra_syntax::{TextRange, TextSize};
     use rustc_hash::FxHashMap;
@@ -283,7 +280,7 @@ mod tests {
                 }
 
                 for f in fns {
-                    let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
+                    let mut sink = DiagnosticSink::new(&mut cb);
                     validate_body(self, f.into(), &mut sink);
                 }
             }
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 897177d05d..e029af0dc9 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -7,7 +7,7 @@
 use std::cell::RefCell;
 
 use hir::{
-    diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder},
+    diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink},
     HasSource, HirDisplay, Semantics, VariantDef,
 };
 use itertools::Itertools;
@@ -29,11 +29,7 @@ pub enum Severity {
     WeakWarning,
 }
 
-pub(crate) fn diagnostics(
-    db: &RootDatabase,
-    file_id: FileId,
-    enable_experimental: bool,
-) -> Vec<Diagnostic> {
+pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> {
     let _p = profile("diagnostics");
     let sema = Semantics::new(db);
     let parse = db.parse(file_id);
@@ -52,85 +48,79 @@ pub(crate) fn diagnostics(
         check_struct_shorthand_initialization(&mut res, file_id, &node);
     }
     let res = RefCell::new(res);
-    let mut sink = DiagnosticSinkBuilder::new()
-        .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
-            let original_file = d.source().file_id.original_file(db);
-            let fix = Fix::new(
-                "Create module",
-                FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }
-                    .into(),
-            );
-            res.borrow_mut().push(Diagnostic {
-                range: sema.diagnostics_range(d).range,
-                message: d.message(),
-                severity: Severity::Error,
-                fix: Some(fix),
-            })
+    let mut sink = DiagnosticSink::new(|d| {
+        res.borrow_mut().push(Diagnostic {
+            message: d.message(),
+            range: sema.diagnostics_range(d).range,
+            severity: Severity::Error,
+            fix: None,
+        })
+    })
+    .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
+        let original_file = d.source().file_id.original_file(db);
+        let fix = Fix::new(
+            "Create module",
+            FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(),
+        );
+        res.borrow_mut().push(Diagnostic {
+            range: sema.diagnostics_range(d).range,
+            message: d.message(),
+            severity: Severity::Error,
+            fix: Some(fix),
         })
-        .on::<hir::diagnostics::MissingFields, _>(|d| {
-            // Note that although we could add a diagnostics to
-            // fill the missing tuple field, e.g :
-            // `struct A(usize);`
-            // `let a = A { 0: () }`
-            // but it is uncommon usage and it should not be encouraged.
-            let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
-                None
-            } else {
-                let mut field_list = d.ast(db);
-                for f in d.missed_fields.iter() {
-                    let field =
-                        make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
-                    field_list = field_list.append_field(&field);
-                }
-
-                let edit = {
-                    let mut builder = TextEditBuilder::default();
-                    algo::diff(&d.ast(db).syntax(), &field_list.syntax())
-                        .into_text_edit(&mut builder);
-                    builder.finish()
-                };
-                Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
+    })
+    .on::<hir::diagnostics::MissingFields, _>(|d| {
+        // Note that although we could add a diagnostics to
+        // fill the missing tuple field, e.g :
+        // `struct A(usize);`
+        // `let a = A { 0: () }`
+        // but it is uncommon usage and it should not be encouraged.
+        let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
+            None
+        } else {
+            let mut field_list = d.ast(db);
+            for f in d.missed_fields.iter() {
+                let field =
+                    make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
+                field_list = field_list.append_field(&field);
+            }
+
+            let edit = {
+                let mut builder = TextEditBuilder::default();
+                algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
+                builder.finish()
             };
+            Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
+        };
 
-            res.borrow_mut().push(Diagnostic {
-                range: sema.diagnostics_range(d).range,
-                message: d.message(),
-                severity: Severity::Error,
-                fix,
-            })
+        res.borrow_mut().push(Diagnostic {
+            range: sema.diagnostics_range(d).range,
+            message: d.message(),
+            severity: Severity::Error,
+            fix,
         })
-        .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
-            let node = d.ast(db);
-            let replacement = format!("Ok({})", node.syntax());
-            let edit = TextEdit::replace(node.syntax().text_range(), replacement);
-            let source_change = SourceFileEdit { file_id, edit }.into();
-            let fix = Fix::new("Wrap with ok", source_change);
-            res.borrow_mut().push(Diagnostic {
-                range: sema.diagnostics_range(d).range,
-                message: d.message(),
-                severity: Severity::Error,
-                fix: Some(fix),
-            })
+    })
+    .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
+        let node = d.ast(db);
+        let replacement = format!("Ok({})", node.syntax());
+        let edit = TextEdit::replace(node.syntax().text_range(), replacement);
+        let source_change = SourceFileEdit { file_id, edit }.into();
+        let fix = Fix::new("Wrap with ok", source_change);
+        res.borrow_mut().push(Diagnostic {
+            range: sema.diagnostics_range(d).range,
+            message: d.message(),
+            severity: Severity::Error,
+            fix: Some(fix),
         })
-        .on::<hir::diagnostics::NoSuchField, _>(|d| {
-            res.borrow_mut().push(Diagnostic {
-                range: sema.diagnostics_range(d).range,
-                message: d.message(),
-                severity: Severity::Error,
-                fix: missing_struct_field_fix(&sema, file_id, d),
-            })
+    })
+    .on::<hir::diagnostics::NoSuchField, _>(|d| {
+        res.borrow_mut().push(Diagnostic {
+            range: sema.diagnostics_range(d).range,
+            message: d.message(),
+            severity: Severity::Error,
+            fix: missing_struct_field_fix(&sema, file_id, d),
         })
-        // Only collect experimental diagnostics when they're enabled.
-        .filter(|diag| !diag.is_experimental() || enable_experimental)
-        // Diagnostics not handled above get no fix and default treatment.
-        .build(|d| {
-            res.borrow_mut().push(Diagnostic {
-                message: d.message(),
-                range: sema.diagnostics_range(d).range,
-                severity: Severity::Error,
-                fix: None,
-            })
-        });
+    });
 
     if let Some(m) = sema.to_module_def(file_id) {
         m.diagnostics(db, &mut sink);
@@ -308,7 +298,7 @@ mod tests {
         let after = trim_indent(ra_fixture_after);
 
         let (analysis, file_position) = analysis_and_position(ra_fixture_before);
-        let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap();
+        let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
         let mut fix = diagnostic.fix.unwrap();
         let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
         let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
@@ -334,7 +324,7 @@ mod tests {
         let ra_fixture_after = &trim_indent(ra_fixture_after);
         let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
         let current_file_id = file_pos.file_id;
-        let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap();
+        let diagnostic = analysis.diagnostics(current_file_id).unwrap().pop().unwrap();
         let mut fix = diagnostic.fix.unwrap();
         let edit = fix.source_change.source_file_edits.pop().unwrap();
         let changed_file_id = edit.file_id;
@@ -355,14 +345,14 @@ mod tests {
         let analysis = mock.analysis();
         let diagnostics = files
             .into_iter()
-            .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap())
+            .flat_map(|file_id| analysis.diagnostics(file_id).unwrap())
             .collect::<Vec<_>>();
         assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
     }
 
     fn check_expect(ra_fixture: &str, expect: Expect) {
         let (analysis, file_id) = single_file(ra_fixture);
-        let diagnostics = analysis.diagnostics(file_id, true).unwrap();
+        let diagnostics = analysis.diagnostics(file_id).unwrap();
         expect.assert_debug_eq(&diagnostics)
     }
 
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 4c4d9f6fa9..dc9192d42c 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -487,12 +487,8 @@ impl Analysis {
     }
 
     /// Computes the set of diagnostics for the given file.
-    pub fn diagnostics(
-        &self,
-        file_id: FileId,
-        enable_experimental: bool,
-    ) -> Cancelable<Vec<Diagnostic>> {
-        self.with_db(|db| diagnostics::diagnostics(db, file_id, enable_experimental))
+    pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> {
+        self.with_db(|db| diagnostics::diagnostics(db, file_id))
     }
 
     /// Returns the edit required to rename reference at the position to the new
@@ -509,10 +505,9 @@ impl Analysis {
         &self,
         query: &str,
         parse_only: bool,
-        position: FilePosition,
     ) -> Cancelable<Result<SourceChange, SsrError>> {
         self.with_db(|db| {
-            let edits = ssr::parse_search_replace(query, parse_only, db, position)?;
+            let edits = ssr::parse_search_replace(query, parse_only, db)?;
             Ok(SourceChange::from(edits))
         })
     }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 95d8f79b87..b3e9e5dfe1 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,5 +1,5 @@
-use ra_db::FilePosition;
-use ra_ide_db::RootDatabase;
+use ra_db::SourceDatabaseExt;
+use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
 
 use crate::SourceFileEdit;
 use ra_ssr::{MatchFinder, SsrError, SsrRule};
@@ -11,19 +11,6 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
 // 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.
 //
-// All paths in both the search pattern and the replacement template must resolve in the context
-// in which this command is invoked. Paths in the search pattern will then match the code if they
-// resolve to the same item, even if they're written differently. For example if we invoke the
-// command in the module `foo` with a pattern of `Bar`, then code in the parent module that refers
-// to `foo::Bar` will match.
-//
-// Paths in the replacement template will be rendered appropriately for the context in which the
-// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
-// code in the `foo` module, we'll insert just `Bar`.
-//
-// Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match
-// `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`.
-//
 // Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
 //
 // Supported constraints:
@@ -56,13 +43,21 @@ pub fn parse_search_replace(
     rule: &str,
     parse_only: bool,
     db: &RootDatabase,
-    position: FilePosition,
 ) -> Result<Vec<SourceFileEdit>, SsrError> {
+    let mut edits = vec![];
     let rule: SsrRule = rule.parse()?;
-    let mut match_finder = MatchFinder::in_context(db, position);
-    match_finder.add_rule(rule)?;
     if parse_only {
-        return Ok(Vec::new());
+        return Ok(edits);
+    }
+    let mut match_finder = MatchFinder::new(db);
+    match_finder.add_rule(rule);
+    for &root in db.local_roots().iter() {
+        let sr = db.source_root(root);
+        for file_id in sr.iter() {
+            if let Some(edit) = match_finder.edits_for_file(file_id) {
+                edits.push(SourceFileEdit { file_id, edit });
+            }
+        }
     }
-    Ok(match_finder.edits())
+    Ok(edits)
 }
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index f391a8e432..e06b189a00 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -290,25 +290,20 @@ pub fn classify_name_ref(
 
     let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
     let resolved = sema.resolve_path(&path)?;
-    Some(NameRefClass::Definition(resolved.into()))
-}
-
-impl From<PathResolution> for Definition {
-    fn from(path_resolution: PathResolution) -> Self {
-        match path_resolution {
-            PathResolution::Def(def) => Definition::ModuleDef(def),
-            PathResolution::AssocItem(item) => {
-                let def = match item {
-                    hir::AssocItem::Function(it) => it.into(),
-                    hir::AssocItem::Const(it) => it.into(),
-                    hir::AssocItem::TypeAlias(it) => it.into(),
-                };
-                Definition::ModuleDef(def)
-            }
-            PathResolution::Local(local) => Definition::Local(local),
-            PathResolution::TypeParam(par) => Definition::TypeParam(par),
-            PathResolution::Macro(def) => Definition::Macro(def),
-            PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
+    let res = match resolved {
+        PathResolution::Def(def) => Definition::ModuleDef(def),
+        PathResolution::AssocItem(item) => {
+            let def = match item {
+                hir::AssocItem::Function(it) => it.into(),
+                hir::AssocItem::Const(it) => it.into(),
+                hir::AssocItem::TypeAlias(it) => it.into(),
+            };
+            Definition::ModuleDef(def)
         }
-    }
+        PathResolution::Local(local) => Definition::Local(local),
+        PathResolution::TypeParam(par) => Definition::TypeParam(par),
+        PathResolution::Macro(def) => Definition::Macro(def),
+        PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
+    };
+    Some(NameRefClass::Definition(res))
 }
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
index a7cae37b0c..81553150b5 100644
--- a/crates/ra_ide_db/src/search.rs
+++ b/crates/ra_ide_db/src/search.rs
@@ -60,10 +60,6 @@ impl SearchScope {
         SearchScope::new(std::iter::once((file, None)).collect())
     }
 
-    pub fn files(files: &[FileId]) -> SearchScope {
-        SearchScope::new(files.iter().map(|f| (*f, None)).collect())
-    }
-
     pub fn intersection(&self, other: &SearchScope) -> SearchScope {
         let (mut small, mut large) = (&self.entries, &other.entries);
         if small.len() > large.len() {
diff --git a/crates/ra_prof/src/memory_usage.rs b/crates/ra_prof/src/memory_usage.rs
index 745345fac0..ee79ec3eee 100644
--- a/crates/ra_prof/src/memory_usage.rs
+++ b/crates/ra_prof/src/memory_usage.rs
@@ -31,12 +31,6 @@ impl fmt::Display for MemoryUsage {
 #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
 pub struct Bytes(usize);
 
-impl Bytes {
-    pub fn megabytes(self) -> usize {
-        self.0 / 1024 / 1024
-    }
-}
-
 impl fmt::Display for Bytes {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let bytes = self.0;
diff --git a/crates/ra_ssr/Cargo.toml b/crates/ra_ssr/Cargo.toml
index 84e4b171e1..fe098aaee3 100644
--- a/crates/ra_ssr/Cargo.toml
+++ b/crates/ra_ssr/Cargo.toml
@@ -18,6 +18,3 @@ ra_ide_db = { path = "../ra_ide_db" }
 hir = { path = "../ra_hir", package = "ra_hir" }
 rustc-hash = "1.1.0"
 test_utils = { path = "../test_utils" }
-
-[dev-dependencies]
-expect = { path = "../expect" }
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index 2fb326b45f..cca4576ce0 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -4,41 +4,44 @@
 //! based on a template.
 
 mod matching;
-mod nester;
 mod parsing;
 mod replacing;
-mod resolving;
-mod search;
 #[macro_use]
 mod errors;
 #[cfg(test)]
 mod tests;
 
-use crate::errors::bail;
 pub use crate::errors::SsrError;
 pub use crate::matching::Match;
-use crate::matching::MatchFailureReason;
+use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason};
 use hir::Semantics;
-use ra_db::{FileId, FilePosition, FileRange};
-use ra_ide_db::source_change::SourceFileEdit;
-use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
-use resolving::ResolvedRule;
+use ra_db::{FileId, FileRange};
+use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange};
+use ra_text_edit::TextEdit;
 use rustc_hash::FxHashMap;
 
 // A structured search replace rule. Create by calling `parse` on a str.
 #[derive(Debug)]
 pub struct SsrRule {
     /// A structured pattern that we're searching for.
-    pattern: parsing::RawPattern,
+    pattern: SsrPattern,
     /// What we'll replace it with.
-    template: parsing::RawPattern,
-    parsed_rules: Vec<parsing::ParsedRule>,
+    template: parsing::SsrTemplate,
 }
 
 #[derive(Debug)]
 pub struct SsrPattern {
-    raw: parsing::RawPattern,
-    parsed_rules: Vec<parsing::ParsedRule>,
+    raw: parsing::RawSearchPattern,
+    /// Placeholders keyed by the stand-in ident that we use in Rust source code.
+    placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
+    // We store our search pattern, parsed as each different kind of thing we can look for. As we
+    // traverse the AST, we get the appropriate one of these for the type of node we're on. For many
+    // search patterns, only some of these will be present.
+    expr: Option<SyntaxNode>,
+    type_ref: Option<SyntaxNode>,
+    item: Option<SyntaxNode>,
+    path: Option<SyntaxNode>,
+    pattern: Option<SyntaxNode>,
 }
 
 #[derive(Debug, Default)]
@@ -50,112 +53,40 @@ pub struct SsrMatches {
 pub struct MatchFinder<'db> {
     /// Our source of information about the user's code.
     sema: Semantics<'db, ra_ide_db::RootDatabase>,
-    rules: Vec<ResolvedRule>,
-    scope: hir::SemanticsScope<'db>,
-    hygiene: hir::Hygiene,
+    rules: Vec<SsrRule>,
 }
 
 impl<'db> MatchFinder<'db> {
-    /// Constructs a new instance where names will be looked up as if they appeared at
-    /// `lookup_context`.
-    pub fn in_context(
-        db: &'db ra_ide_db::RootDatabase,
-        lookup_context: FilePosition,
-    ) -> MatchFinder<'db> {
-        let sema = Semantics::new(db);
-        let file = sema.parse(lookup_context.file_id);
-        // Find a node at the requested position, falling back to the whole file.
-        let node = file
-            .syntax()
-            .token_at_offset(lookup_context.offset)
-            .left_biased()
-            .map(|token| token.parent())
-            .unwrap_or_else(|| file.syntax().clone());
-        let scope = sema.scope(&node);
-        MatchFinder {
-            sema: Semantics::new(db),
-            rules: Vec::new(),
-            scope,
-            hygiene: hir::Hygiene::new(db, lookup_context.file_id.into()),
-        }
+    pub fn new(db: &'db ra_ide_db::RootDatabase) -> MatchFinder<'db> {
+        MatchFinder { sema: Semantics::new(db), rules: Vec::new() }
     }
 
-    /// Constructs an instance using the start of the first file in `db` as the lookup context.
-    pub fn at_first_file(db: &'db ra_ide_db::RootDatabase) -> Result<MatchFinder<'db>, SsrError> {
-        use ra_db::SourceDatabaseExt;
-        use ra_ide_db::symbol_index::SymbolsDatabase;
-        if let Some(first_file_id) = db
-            .local_roots()
-            .iter()
-            .next()
-            .and_then(|root| db.source_root(root.clone()).iter().next())
-        {
-            Ok(MatchFinder::in_context(
-                db,
-                FilePosition { file_id: first_file_id, offset: 0.into() },
-            ))
-        } else {
-            bail!("No files to search");
-        }
-    }
-
-    /// Adds a rule to be applied. The order in which rules are added matters. Earlier rules take
-    /// precedence. If a node is matched by an earlier rule, then later rules won't be permitted to
-    /// match to it.
-    pub fn add_rule(&mut self, rule: SsrRule) -> Result<(), SsrError> {
-        for parsed_rule in rule.parsed_rules {
-            self.rules.push(ResolvedRule::new(
-                parsed_rule,
-                &self.scope,
-                &self.hygiene,
-                self.rules.len(),
-            )?);
-        }
-        Ok(())
-    }
-
-    /// Finds matches for all added rules and returns edits for all found matches.
-    pub fn edits(&self) -> Vec<SourceFileEdit> {
-        use ra_db::SourceDatabaseExt;
-        let mut matches_by_file = FxHashMap::default();
-        for m in self.matches().matches {
-            matches_by_file
-                .entry(m.range.file_id)
-                .or_insert_with(|| SsrMatches::default())
-                .matches
-                .push(m);
-        }
-        let mut edits = vec![];
-        for (file_id, matches) in matches_by_file {
-            let edit =
-                replacing::matches_to_edit(&matches, &self.sema.db.file_text(file_id), &self.rules);
-            edits.push(SourceFileEdit { file_id, edit });
-        }
-        edits
+    pub fn add_rule(&mut self, rule: SsrRule) {
+        self.rules.push(rule);
     }
 
     /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
     /// intend to do replacement, use `add_rule` instead.
-    pub fn add_search_pattern(&mut self, pattern: SsrPattern) -> Result<(), SsrError> {
-        for parsed_rule in pattern.parsed_rules {
-            self.rules.push(ResolvedRule::new(
-                parsed_rule,
-                &self.scope,
-                &self.hygiene,
-                self.rules.len(),
-            )?);
-        }
-        Ok(())
+    pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
+        self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
     }
 
-    /// Returns matches for all added rules.
-    pub fn matches(&self) -> SsrMatches {
-        let mut matches = Vec::new();
-        let mut usage_cache = search::UsageCache::default();
-        for rule in &self.rules {
-            self.find_matches_for_rule(rule, &mut usage_cache, &mut matches);
+    pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
+        let matches = self.find_matches_in_file(file_id);
+        if matches.matches.is_empty() {
+            None
+        } else {
+            use ra_db::SourceDatabaseExt;
+            Some(replacing::matches_to_edit(&matches, &self.sema.db.file_text(file_id)))
         }
-        nester::nest_and_remove_collisions(matches, &self.sema)
+    }
+
+    pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
+        let file = self.sema.parse(file_id);
+        let code = file.syntax();
+        let mut matches = SsrMatches::default();
+        self.find_matches(code, &None, &mut matches);
+        matches
     }
 
     /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
@@ -184,6 +115,53 @@ impl<'db> MatchFinder<'db> {
         res
     }
 
+    fn find_matches(
+        &self,
+        code: &SyntaxNode,
+        restrict_range: &Option<FileRange>,
+        matches_out: &mut SsrMatches,
+    ) {
+        for rule in &self.rules {
+            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,
+                            );
+                        }
+                    }
+                }
+                matches_out.matches.push(m);
+                return;
+            }
+        }
+        // If we've got a macro call, we already tried matching it pre-expansion, which is the only
+        // way to match the whole macro, now try expanding it and matching the expansion.
+        if let Some(macro_call) = ast::MacroCall::cast(code.clone()) {
+            if let Some(expanded) = self.sema.expand(&macro_call) {
+                if let Some(tt) = macro_call.token_tree() {
+                    // When matching within a macro expansion, we only want to allow matches of
+                    // nodes that originated entirely from within the token tree of the macro call.
+                    // i.e. we don't want to match something that came from the macro itself.
+                    self.find_matches(
+                        &expanded,
+                        &Some(self.sema.original_range(tt.syntax())),
+                        matches_out,
+                    );
+                }
+            }
+        }
+        for child in code.children() {
+            self.find_matches(&child, restrict_range, matches_out);
+        }
+    }
+
     fn output_debug_for_nodes_at_range(
         &self,
         node: &SyntaxNode,
@@ -199,17 +177,8 @@ impl<'db> MatchFinder<'db> {
             }
             if node_range.range == range.range {
                 for rule in &self.rules {
-                    // For now we ignore rules that have a different kind than our node, otherwise
-                    // we get lots of noise. If at some point we add support for restricting rules
-                    // to a particular kind of thing (e.g. only match type references), then we can
-                    // relax this. We special-case expressions, since function calls can match
-                    // method calls.
-                    if rule.pattern.node.kind() != node.kind()
-                        && !(ast::Expr::can_cast(rule.pattern.node.kind())
-                            && ast::Expr::can_cast(node.kind()))
-                    {
-                        continue;
-                    }
+                    let pattern =
+                        rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
                     out.push(MatchDebugInfo {
                         matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
                             .map_err(|e| MatchFailureReason {
@@ -217,7 +186,7 @@ impl<'db> MatchFinder<'db> {
                                     "Match failed, but no reason was given".to_owned()
                                 }),
                             }),
-                        pattern: rule.pattern.node.clone(),
+                        pattern,
                         node: node.clone(),
                     });
                 }
@@ -240,8 +209,9 @@ impl<'db> MatchFinder<'db> {
 
 pub struct MatchDebugInfo {
     node: SyntaxNode,
-    /// Our search pattern parsed as an expression or item, etc
-    pattern: SyntaxNode,
+    /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
+    /// etc. Will be absent if the pattern can't be parsed as that kind.
+    pattern: Result<SyntaxNode, MatchFailureReason>,
     matched: Result<Match, MatchFailureReason>,
 }
 
@@ -258,12 +228,29 @@ impl std::fmt::Debug for MatchDebugInfo {
             self.node
         )?;
         writeln!(f, "========= PATTERN ==========")?;
-        writeln!(f, "{:#?}", self.pattern)?;
+        match &self.pattern {
+            Ok(pattern) => {
+                writeln!(f, "{:#?}", pattern)?;
+            }
+            Err(err) => {
+                writeln!(f, "{}", err.reason)?;
+            }
+        }
         writeln!(f, "============================")?;
         Ok(())
     }
 }
 
+impl SsrPattern {
+    fn tree_for_kind_with_reason(
+        &self,
+        kind: SyntaxKind,
+    ) -> Result<&SyntaxNode, MatchFailureReason> {
+        record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
+            .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
+    }
+}
+
 impl SsrMatches {
     /// Returns `self` with any nested matches removed and made into top-level matches.
     pub fn flattened(self) -> SsrMatches {
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 4862622bde..50b29eab2d 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -2,9 +2,8 @@
 //! process of matching, placeholder values are recorded.
 
 use crate::{
-    parsing::{Constraint, NodeKind, Placeholder},
-    resolving::{ResolvedPattern, ResolvedRule},
-    SsrMatches,
+    parsing::{Constraint, NodeKind, Placeholder, SsrTemplate},
+    SsrMatches, SsrPattern, SsrRule,
 };
 use hir::Semantics;
 use ra_db::FileRange;
@@ -49,11 +48,9 @@ pub struct Match {
     pub(crate) matched_node: SyntaxNode,
     pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
     pub(crate) ignored_comments: Vec<ast::Comment>,
-    pub(crate) rule_index: usize,
-    /// The depth of matched_node.
-    pub(crate) depth: usize,
-    // Each path in the template rendered for the module in which the match was found.
-    pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
+    // A copy of the template for the rule that produced this match. We store this on the match for
+    // if/when we do replacement.
+    pub(crate) template: SsrTemplate,
 }
 
 /// Represents a `$var` in an SSR query.
@@ -89,7 +86,7 @@ pub(crate) struct MatchFailed {
 /// parent module, we don't populate nested matches.
 pub(crate) fn get_match(
     debug_active: bool,
-    rule: &ResolvedRule,
+    rule: &SsrRule,
     code: &SyntaxNode,
     restrict_range: &Option<FileRange>,
     sema: &Semantics<ra_ide_db::RootDatabase>,
@@ -105,7 +102,7 @@ struct Matcher<'db, 'sema> {
     /// If any placeholders come from anywhere outside of this range, then the match will be
     /// rejected.
     restrict_range: Option<FileRange>,
-    rule: &'sema ResolvedRule,
+    rule: &'sema SsrRule,
 }
 
 /// Which phase of matching we're currently performing. We do two phases because most attempted
@@ -120,35 +117,26 @@ enum Phase<'a> {
 
 impl<'db, 'sema> Matcher<'db, 'sema> {
     fn try_match(
-        rule: &ResolvedRule,
+        rule: &'sema SsrRule,
         code: &SyntaxNode,
         restrict_range: &Option<FileRange>,
         sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
     ) -> Result<Match, MatchFailed> {
         let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule };
+        let pattern_tree = rule.pattern.tree_for_kind(code.kind())?;
         // First pass at matching, where we check that node types and idents match.
-        match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
+        match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?;
         match_state.validate_range(&sema.original_range(code))?;
         let mut the_match = Match {
             range: sema.original_range(code),
             matched_node: code.clone(),
             placeholder_values: FxHashMap::default(),
             ignored_comments: Vec::new(),
-            rule_index: rule.index,
-            depth: 0,
-            rendered_template_paths: FxHashMap::default(),
+            template: rule.template.clone(),
         };
         // Second matching pass, where we record placeholder matches, ignored comments and maybe do
         // any other more expensive checks that we didn't want to do on the first pass.
-        match_state.attempt_match_node(
-            &mut Phase::Second(&mut the_match),
-            &rule.pattern.node,
-            code,
-        )?;
-        the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count();
-        if let Some(template) = &rule.template {
-            the_match.render_template_paths(template, sema)?;
-        }
+        match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?;
         Ok(the_match)
     }
 
@@ -189,17 +177,10 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
             }
             return Ok(());
         }
-        // We allow a UFCS call to match a method call, provided they resolve to the same function.
-        if let Some(pattern_function) = self.rule.pattern.ufcs_function_calls.get(pattern) {
-            if let (Some(pattern), Some(code)) =
-                (ast::CallExpr::cast(pattern.clone()), ast::MethodCallExpr::cast(code.clone()))
-            {
-                return self.attempt_match_ufcs(phase, &pattern, &code, *pattern_function);
-            }
-        }
+        // Non-placeholders.
         if pattern.kind() != code.kind() {
             fail_match!(
-                "Pattern had `{}` ({:?}), code had `{}` ({:?})",
+                "Pattern had a `{}` ({:?}), code had `{}` ({:?})",
                 pattern.text(),
                 pattern.kind(),
                 code.text(),
@@ -213,7 +194,6 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
                 self.attempt_match_record_field_list(phase, pattern, code)
             }
             SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
-            SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
             _ => self.attempt_match_node_children(phase, pattern, code),
         }
     }
@@ -330,64 +310,6 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
         Ok(())
     }
 
-    /// Paths are matched based on whether they refer to the same thing, even if they're written
-    /// differently.
-    fn attempt_match_path(
-        &self,
-        phase: &mut Phase,
-        pattern: &SyntaxNode,
-        code: &SyntaxNode,
-    ) -> Result<(), MatchFailed> {
-        if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
-            let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
-            let code_path = ast::Path::cast(code.clone()).unwrap();
-            if let (Some(pattern_segment), Some(code_segment)) =
-                (pattern_path.segment(), code_path.segment())
-            {
-                // Match everything within the segment except for the name-ref, which is handled
-                // separately via comparing what the path resolves to below.
-                self.attempt_match_opt(
-                    phase,
-                    pattern_segment.type_arg_list(),
-                    code_segment.type_arg_list(),
-                )?;
-                self.attempt_match_opt(
-                    phase,
-                    pattern_segment.param_list(),
-                    code_segment.param_list(),
-                )?;
-            }
-            if matches!(phase, Phase::Second(_)) {
-                let resolution = self
-                    .sema
-                    .resolve_path(&code_path)
-                    .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
-                if pattern_resolved.resolution != resolution {
-                    fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
-                }
-            }
-        } else {
-            return self.attempt_match_node_children(phase, pattern, code);
-        }
-        Ok(())
-    }
-
-    fn attempt_match_opt<T: AstNode>(
-        &self,
-        phase: &mut Phase,
-        pattern: Option<T>,
-        code: Option<T>,
-    ) -> Result<(), MatchFailed> {
-        match (pattern, code) {
-            (Some(p), Some(c)) => self.attempt_match_node(phase, &p.syntax(), &c.syntax()),
-            (None, None) => Ok(()),
-            (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
-            (None, Some(c)) => {
-                fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
-            }
-        }
-    }
-
     /// We want to allow the records to match in any order, so we have special matching logic for
     /// them.
     fn attempt_match_record_field_list(
@@ -521,61 +443,9 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
         Ok(())
     }
 
-    fn attempt_match_ufcs(
-        &self,
-        phase: &mut Phase,
-        pattern: &ast::CallExpr,
-        code: &ast::MethodCallExpr,
-        pattern_function: hir::Function,
-    ) -> Result<(), MatchFailed> {
-        use ast::ArgListOwner;
-        let code_resolved_function = self
-            .sema
-            .resolve_method_call(code)
-            .ok_or_else(|| match_error!("Failed to resolve method call"))?;
-        if pattern_function != code_resolved_function {
-            fail_match!("Method call resolved to a different function");
-        }
-        // Check arguments.
-        let mut pattern_args = pattern
-            .arg_list()
-            .ok_or_else(|| match_error!("Pattern function call has no args"))?
-            .args();
-        self.attempt_match_opt(phase, pattern_args.next(), code.expr())?;
-        let mut code_args =
-            code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
-        loop {
-            match (pattern_args.next(), code_args.next()) {
-                (None, None) => return Ok(()),
-                (p, c) => self.attempt_match_opt(phase, p, c)?,
-            }
-        }
-    }
-
     fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
-        only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
-    }
-}
-
-impl Match {
-    fn render_template_paths(
-        &mut self,
-        template: &ResolvedPattern,
-        sema: &Semantics<ra_ide_db::RootDatabase>,
-    ) -> Result<(), MatchFailed> {
-        let module = sema
-            .scope(&self.matched_node)
-            .module()
-            .ok_or_else(|| match_error!("Matched node isn't in a module"))?;
-        for (path, resolved_path) in &template.resolved_paths {
-            if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
-                let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| {
-                    match_error!("Failed to render template path `{}` at match location")
-                })?;
-                self.rendered_template_paths.insert(path.clone(), mod_path);
-            }
-        }
-        Ok(())
+        only_ident(element.clone())
+            .and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text()))
     }
 }
 
@@ -640,6 +510,28 @@ impl PlaceholderMatch {
     }
 }
 
+impl SsrPattern {
+    pub(crate) fn tree_for_kind(&self, kind: SyntaxKind) -> Result<&SyntaxNode, MatchFailed> {
+        let (tree, kind_name) = if ast::Expr::can_cast(kind) {
+            (&self.expr, "expression")
+        } else if ast::TypeRef::can_cast(kind) {
+            (&self.type_ref, "type reference")
+        } else if ast::ModuleItem::can_cast(kind) {
+            (&self.item, "item")
+        } else if ast::Path::can_cast(kind) {
+            (&self.path, "path")
+        } else if ast::Pat::can_cast(kind) {
+            (&self.pattern, "pattern")
+        } else {
+            fail_match!("Matching nodes of kind {:?} is not supported", kind);
+        };
+        match tree {
+            Some(tree) => Ok(tree),
+            None => fail_match!("Pattern cannot be parsed as a {}", kind_name),
+        }
+    }
+}
+
 impl NodeKind {
     fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
         let ok = match self {
@@ -704,12 +596,13 @@ mod tests {
     #[test]
     fn parse_match_replace() {
         let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
-        let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
+        let input = "fn foo() {} fn main() { foo(1+2); }";
 
-        let (db, position) = crate::tests::single_file(input);
-        let mut match_finder = MatchFinder::in_context(&db, position);
-        match_finder.add_rule(rule).unwrap();
-        let matches = match_finder.matches();
+        use ra_db::fixture::WithFixture;
+        let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input);
+        let mut match_finder = MatchFinder::new(&db);
+        match_finder.add_rule(rule);
+        let matches = match_finder.find_matches_in_file(file_id);
         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);
@@ -722,11 +615,9 @@ mod tests {
             "1+2"
         );
 
-        let edits = match_finder.edits();
-        assert_eq!(edits.len(), 1);
-        let edit = &edits[0];
+        let edit = crate::replacing::matches_to_edit(&matches, input);
         let mut after = input.to_string();
-        edit.edit.apply(&mut after);
-        assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
+        edit.apply(&mut after);
+        assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }");
     }
 }
diff --git a/crates/ra_ssr/src/nester.rs b/crates/ra_ssr/src/nester.rs
deleted file mode 100644
index b3e20579bd..0000000000
--- a/crates/ra_ssr/src/nester.rs
+++ /dev/null
@@ -1,98 +0,0 @@
-//! Converts a flat collection of matches into a nested form suitable for replacement. When there
-//! are multiple matches for a node, or that overlap, priority is given to the earlier rule. Nested
-//! matches are only permitted if the inner match is contained entirely within a placeholder of an
-//! outer match.
-//!
-//! For example, if our search pattern is `foo(foo($a))` and the code had `foo(foo(foo(foo(42))))`,
-//! then we'll get 3 matches, however only the outermost and innermost matches can be accepted. The
-//! middle match would take the second `foo` from the outer match.
-
-use crate::{Match, SsrMatches};
-use ra_syntax::SyntaxNode;
-use rustc_hash::FxHashMap;
-
-pub(crate) fn nest_and_remove_collisions(
-    mut matches: Vec<Match>,
-    sema: &hir::Semantics<ra_ide_db::RootDatabase>,
-) -> SsrMatches {
-    // We sort the matches by depth then by rule index. Sorting by depth means that by the time we
-    // see a match, any parent matches or conflicting matches will have already been seen. Sorting
-    // by rule_index means that if there are two matches for the same node, the rule added first
-    // will take precedence.
-    matches.sort_by(|a, b| a.depth.cmp(&b.depth).then_with(|| a.rule_index.cmp(&b.rule_index)));
-    let mut collector = MatchCollector::default();
-    for m in matches {
-        collector.add_match(m, sema);
-    }
-    collector.into()
-}
-
-#[derive(Default)]
-struct MatchCollector {
-    matches_by_node: FxHashMap<SyntaxNode, Match>,
-}
-
-impl MatchCollector {
-    /// Attempts to add `m` to matches. If it conflicts with an existing match, it is discarded. If
-    /// it is entirely within the a placeholder of an existing match, then it is added as a child
-    /// match of the existing match.
-    fn add_match(&mut self, m: Match, sema: &hir::Semantics<ra_ide_db::RootDatabase>) {
-        let matched_node = m.matched_node.clone();
-        if let Some(existing) = self.matches_by_node.get_mut(&matched_node) {
-            try_add_sub_match(m, existing, sema);
-            return;
-        }
-        for ancestor in sema.ancestors_with_macros(m.matched_node.clone()) {
-            if let Some(existing) = self.matches_by_node.get_mut(&ancestor) {
-                try_add_sub_match(m, existing, sema);
-                return;
-            }
-        }
-        self.matches_by_node.insert(matched_node, m);
-    }
-}
-
-/// Attempts to add `m` as a sub-match of `existing`.
-fn try_add_sub_match(
-    m: Match,
-    existing: &mut Match,
-    sema: &hir::Semantics<ra_ide_db::RootDatabase>,
-) {
-    for p in existing.placeholder_values.values_mut() {
-        // Note, no need to check if p.range.file is equal to m.range.file, since we
-        // already know we're within `existing`.
-        if p.range.range.contains_range(m.range.range) {
-            // Convert the inner matches in `p` into a temporary MatchCollector. When
-            // we're done, we then convert it back into an SsrMatches. If we expected
-            // lots of inner matches, it might be worthwhile keeping a MatchCollector
-            // around for each placeholder match. However we expect most placeholder
-            // will have 0 and a few will have 1. More than that should hopefully be
-            // exceptional.
-            let mut collector = MatchCollector::default();
-            for m in std::mem::replace(&mut p.inner_matches.matches, Vec::new()) {
-                collector.matches_by_node.insert(m.matched_node.clone(), m);
-            }
-            collector.add_match(m, sema);
-            p.inner_matches = collector.into();
-            break;
-        }
-    }
-}
-
-impl From<MatchCollector> for SsrMatches {
-    fn from(mut match_collector: MatchCollector) -> Self {
-        let mut matches = SsrMatches::default();
-        for (_, m) in match_collector.matches_by_node.drain() {
-            matches.matches.push(m);
-        }
-        matches.matches.sort_by(|a, b| {
-            // Order matches by file_id then by start range. This should be sufficient since ranges
-            // shouldn't be overlapping.
-            a.range
-                .file_id
-                .cmp(&b.range.file_id)
-                .then_with(|| a.range.range.start().cmp(&b.range.range.start()))
-        });
-        matches
-    }
-}
diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs
index 2d6f4e514f..4aee97bb2b 100644
--- a/crates/ra_ssr/src/parsing.rs
+++ b/crates/ra_ssr/src/parsing.rs
@@ -7,19 +7,17 @@
 
 use crate::errors::bail;
 use crate::{SsrError, SsrPattern, SsrRule};
-use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
+use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T};
 use rustc_hash::{FxHashMap, FxHashSet};
 use std::str::FromStr;
 
-#[derive(Debug)]
-pub(crate) struct ParsedRule {
-    pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
-    pub(crate) pattern: SyntaxNode,
-    pub(crate) template: Option<SyntaxNode>,
+#[derive(Clone, Debug)]
+pub(crate) struct SsrTemplate {
+    pub(crate) tokens: Vec<PatternElement>,
 }
 
 #[derive(Debug)]
-pub(crate) struct RawPattern {
+pub(crate) struct RawSearchPattern {
     tokens: Vec<PatternElement>,
 }
 
@@ -56,60 +54,6 @@ pub(crate) struct Token {
     pub(crate) text: SmolStr,
 }
 
-impl ParsedRule {
-    fn new(
-        pattern: &RawPattern,
-        template: Option<&RawPattern>,
-    ) -> Result<Vec<ParsedRule>, SsrError> {
-        let raw_pattern = pattern.as_rust_code();
-        let raw_template = template.map(|t| t.as_rust_code());
-        let raw_template = raw_template.as_ref().map(|s| s.as_str());
-        let mut builder = RuleBuilder {
-            placeholders_by_stand_in: pattern.placeholders_by_stand_in(),
-            rules: Vec::new(),
-        };
-        builder.try_add(ast::Expr::parse(&raw_pattern), raw_template.map(ast::Expr::parse));
-        builder.try_add(ast::TypeRef::parse(&raw_pattern), raw_template.map(ast::TypeRef::parse));
-        builder.try_add(
-            ast::ModuleItem::parse(&raw_pattern),
-            raw_template.map(ast::ModuleItem::parse),
-        );
-        builder.try_add(ast::Path::parse(&raw_pattern), raw_template.map(ast::Path::parse));
-        builder.try_add(ast::Pat::parse(&raw_pattern), raw_template.map(ast::Pat::parse));
-        builder.build()
-    }
-}
-
-struct RuleBuilder {
-    placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
-    rules: Vec<ParsedRule>,
-}
-
-impl RuleBuilder {
-    fn try_add<T: AstNode>(&mut self, pattern: Result<T, ()>, template: Option<Result<T, ()>>) {
-        match (pattern, template) {
-            (Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule {
-                placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
-                pattern: pattern.syntax().clone(),
-                template: Some(template.syntax().clone()),
-            }),
-            (Ok(pattern), None) => self.rules.push(ParsedRule {
-                placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
-                pattern: pattern.syntax().clone(),
-                template: None,
-            }),
-            _ => {}
-        }
-    }
-
-    fn build(self) -> Result<Vec<ParsedRule>, SsrError> {
-        if self.rules.is_empty() {
-            bail!("Not a valid Rust expression, type, item, path or pattern");
-        }
-        Ok(self.rules)
-    }
-}
-
 impl FromStr for SsrRule {
     type Err = SsrError;
 
@@ -124,24 +68,21 @@ impl FromStr for SsrRule {
         if it.next().is_some() {
             return Err(SsrError("More than one delimiter found".into()));
         }
-        let raw_pattern = pattern.parse()?;
-        let raw_template = template.parse()?;
-        let parsed_rules = ParsedRule::new(&raw_pattern, Some(&raw_template))?;
-        let rule = SsrRule { pattern: raw_pattern, template: raw_template, parsed_rules };
+        let rule = SsrRule { pattern: pattern.parse()?, template: template.parse()? };
         validate_rule(&rule)?;
         Ok(rule)
     }
 }
 
-impl FromStr for RawPattern {
+impl FromStr for RawSearchPattern {
     type Err = SsrError;
 
-    fn from_str(pattern_str: &str) -> Result<RawPattern, SsrError> {
-        Ok(RawPattern { tokens: parse_pattern(pattern_str)? })
+    fn from_str(pattern_str: &str) -> Result<RawSearchPattern, SsrError> {
+        Ok(RawSearchPattern { tokens: parse_pattern(pattern_str)? })
     }
 }
 
-impl RawPattern {
+impl RawSearchPattern {
     /// Returns this search pattern as Rust source code that we can feed to the Rust parser.
     fn as_rust_code(&self) -> String {
         let mut res = String::new();
@@ -154,7 +95,7 @@ impl RawPattern {
         res
     }
 
-    pub(crate) fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> {
+    fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> {
         let mut res = FxHashMap::default();
         for t in &self.tokens {
             if let PatternElement::Placeholder(placeholder) = t {
@@ -169,9 +110,41 @@ impl FromStr for SsrPattern {
     type Err = SsrError;
 
     fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
-        let raw_pattern = pattern_str.parse()?;
-        let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
-        Ok(SsrPattern { raw: raw_pattern, parsed_rules })
+        let raw: RawSearchPattern = pattern_str.parse()?;
+        let raw_str = raw.as_rust_code();
+        let res = SsrPattern {
+            expr: ast::Expr::parse(&raw_str).ok().map(|n| n.syntax().clone()),
+            type_ref: ast::TypeRef::parse(&raw_str).ok().map(|n| n.syntax().clone()),
+            item: ast::ModuleItem::parse(&raw_str).ok().map(|n| n.syntax().clone()),
+            path: ast::Path::parse(&raw_str).ok().map(|n| n.syntax().clone()),
+            pattern: ast::Pat::parse(&raw_str).ok().map(|n| n.syntax().clone()),
+            placeholders_by_stand_in: raw.placeholders_by_stand_in(),
+            raw,
+        };
+        if res.expr.is_none()
+            && res.type_ref.is_none()
+            && res.item.is_none()
+            && res.path.is_none()
+            && res.pattern.is_none()
+        {
+            bail!("Pattern is not a valid Rust expression, type, item, path or pattern");
+        }
+        Ok(res)
+    }
+}
+
+impl FromStr for SsrTemplate {
+    type Err = SsrError;
+
+    fn from_str(pattern_str: &str) -> Result<SsrTemplate, SsrError> {
+        let tokens = parse_pattern(pattern_str)?;
+        // Validate that the template is a valid fragment of Rust code. We reuse the validation
+        // logic for search patterns since the only thing that differs is the error message.
+        if SsrPattern::from_str(pattern_str).is_err() {
+            bail!("Replacement is not a valid Rust expression, type, item, path or pattern");
+        }
+        // Our actual template needs to preserve whitespace, so we can't reuse `tokens`.
+        Ok(SsrTemplate { tokens })
     }
 }
 
@@ -200,7 +173,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
 /// pattern didn't define.
 fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
     let mut defined_placeholders = FxHashSet::default();
-    for p in &rule.pattern.tokens {
+    for p in &rule.pattern.raw.tokens {
         if let PatternElement::Placeholder(placeholder) = p {
             defined_placeholders.insert(&placeholder.ident);
         }
@@ -343,7 +316,7 @@ mod tests {
         }
         let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
         assert_eq!(
-            result.pattern.tokens,
+            result.pattern.raw.tokens,
             vec![
                 token(SyntaxKind::IDENT, "foo"),
                 token(T!['('], "("),
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs
index 4b3f5509c3..e43cc51674 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -1,124 +1,66 @@
 //! Code for applying replacement templates for matches that have previously been found.
 
 use crate::matching::Var;
-use crate::{resolving::ResolvedRule, Match, SsrMatches};
-use ra_syntax::ast::{self, AstToken};
-use ra_syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextSize};
+use crate::parsing::PatternElement;
+use crate::{Match, SsrMatches};
+use ra_syntax::ast::AstToken;
+use ra_syntax::TextSize;
 use ra_text_edit::TextEdit;
 
 /// Returns a text edit that will replace each match in `matches` with its corresponding replacement
 /// template. Placeholders in the template will have been substituted with whatever they matched to
 /// in the original code.
-pub(crate) fn matches_to_edit(
-    matches: &SsrMatches,
-    file_src: &str,
-    rules: &[ResolvedRule],
-) -> TextEdit {
-    matches_to_edit_at_offset(matches, file_src, 0.into(), rules)
+pub(crate) fn matches_to_edit(matches: &SsrMatches, file_src: &str) -> TextEdit {
+    matches_to_edit_at_offset(matches, file_src, 0.into())
 }
 
 fn matches_to_edit_at_offset(
     matches: &SsrMatches,
     file_src: &str,
     relative_start: TextSize,
-    rules: &[ResolvedRule],
 ) -> TextEdit {
     let mut edit_builder = ra_text_edit::TextEditBuilder::default();
     for m in &matches.matches {
         edit_builder.replace(
             m.range.range.checked_sub(relative_start).unwrap(),
-            render_replace(m, file_src, rules),
+            render_replace(m, file_src),
         );
     }
     edit_builder.finish()
 }
 
-struct ReplacementRenderer<'a> {
-    match_info: &'a Match,
-    file_src: &'a str,
-    rules: &'a [ResolvedRule],
-    rule: &'a ResolvedRule,
-}
-
-fn render_replace(match_info: &Match, file_src: &str, rules: &[ResolvedRule]) -> String {
+fn render_replace(match_info: &Match, file_src: &str) -> String {
     let mut out = String::new();
-    let rule = &rules[match_info.rule_index];
-    let template = rule
-        .template
-        .as_ref()
-        .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern");
-    let renderer = ReplacementRenderer { match_info, file_src, rules, rule };
-    renderer.render_node(&template.node, &mut out);
-    for comment in &match_info.ignored_comments {
-        out.push_str(&comment.syntax().to_string());
-    }
-    out
-}
-
-impl ReplacementRenderer<'_> {
-    fn render_node_children(&self, node: &SyntaxNode, out: &mut String) {
-        for node_or_token in node.children_with_tokens() {
-            self.render_node_or_token(&node_or_token, out);
-        }
-    }
-
-    fn render_node_or_token(&self, node_or_token: &SyntaxElement, out: &mut String) {
-        match node_or_token {
-            SyntaxElement::Token(token) => {
-                self.render_token(&token, out);
-            }
-            SyntaxElement::Node(child_node) => {
-                self.render_node(&child_node, out);
-            }
-        }
-    }
-
-    fn render_node(&self, node: &SyntaxNode, out: &mut String) {
-        use ra_syntax::ast::AstNode;
-        if let Some(mod_path) = self.match_info.rendered_template_paths.get(&node) {
-            out.push_str(&mod_path.to_string());
-            // Emit everything except for the segment's name-ref, since we already effectively
-            // emitted that as part of `mod_path`.
-            if let Some(path) = ast::Path::cast(node.clone()) {
-                if let Some(segment) = path.segment() {
-                    for node_or_token in segment.syntax().children_with_tokens() {
-                        if node_or_token.kind() != SyntaxKind::NAME_REF {
-                            self.render_node_or_token(&node_or_token, out);
-                        }
-                    }
+    for r in &match_info.template.tokens {
+        match r {
+            PatternElement::Token(t) => out.push_str(t.text.as_str()),
+            PatternElement::Placeholder(p) => {
+                if let Some(placeholder_value) =
+                    match_info.placeholder_values.get(&Var(p.ident.to_string()))
+                {
+                    let range = &placeholder_value.range.range;
+                    let mut matched_text =
+                        file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
+                    let edit = matches_to_edit_at_offset(
+                        &placeholder_value.inner_matches,
+                        file_src,
+                        range.start(),
+                    );
+                    edit.apply(&mut matched_text);
+                    out.push_str(&matched_text);
+                } else {
+                    // We validated that all placeholder references were valid before we
+                    // started, so this shouldn't happen.
+                    panic!(
+                        "Internal error: replacement referenced unknown placeholder {}",
+                        p.ident
+                    );
                 }
             }
-        } else {
-            self.render_node_children(&node, out);
         }
     }
-
-    fn render_token(&self, token: &SyntaxToken, out: &mut String) {
-        if let Some(placeholder) = self.rule.get_placeholder(&token) {
-            if let Some(placeholder_value) =
-                self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string()))
-            {
-                let range = &placeholder_value.range.range;
-                let mut matched_text =
-                    self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
-                let edit = matches_to_edit_at_offset(
-                    &placeholder_value.inner_matches,
-                    self.file_src,
-                    range.start(),
-                    self.rules,
-                );
-                edit.apply(&mut matched_text);
-                out.push_str(&matched_text);
-            } else {
-                // We validated that all placeholder references were valid before we
-                // started, so this shouldn't happen.
-                panic!(
-                    "Internal error: replacement referenced unknown placeholder {}",
-                    placeholder.ident
-                );
-            }
-        } else {
-            out.push_str(token.text().as_str());
-        }
+    for comment in &match_info.ignored_comments {
+        out.push_str(&comment.syntax().to_string());
     }
+    out
 }
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs
deleted file mode 100644
index 75f5567856..0000000000
--- a/crates/ra_ssr/src/resolving.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-//! This module is responsible for resolving paths within rules.
-
-use crate::errors::error;
-use crate::{parsing, SsrError};
-use parsing::Placeholder;
-use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
-use rustc_hash::{FxHashMap, FxHashSet};
-use test_utils::mark;
-
-pub(crate) struct ResolvedRule {
-    pub(crate) pattern: ResolvedPattern,
-    pub(crate) template: Option<ResolvedPattern>,
-    pub(crate) index: usize,
-}
-
-pub(crate) struct ResolvedPattern {
-    pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
-    pub(crate) node: SyntaxNode,
-    // Paths in `node` that we've resolved.
-    pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
-    pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
-}
-
-pub(crate) struct ResolvedPath {
-    pub(crate) resolution: hir::PathResolution,
-    /// The depth of the ast::Path that was resolved within the pattern.
-    pub(crate) depth: u32,
-}
-
-impl ResolvedRule {
-    pub(crate) fn new(
-        rule: parsing::ParsedRule,
-        scope: &hir::SemanticsScope,
-        hygiene: &hir::Hygiene,
-        index: usize,
-    ) -> Result<ResolvedRule, SsrError> {
-        let resolver =
-            Resolver { scope, hygiene, placeholders_by_stand_in: rule.placeholders_by_stand_in };
-        let resolved_template = if let Some(template) = rule.template {
-            Some(resolver.resolve_pattern_tree(template)?)
-        } else {
-            None
-        };
-        Ok(ResolvedRule {
-            pattern: resolver.resolve_pattern_tree(rule.pattern)?,
-            template: resolved_template,
-            index,
-        })
-    }
-
-    pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
-        if token.kind() != SyntaxKind::IDENT {
-            return None;
-        }
-        self.pattern.placeholders_by_stand_in.get(token.text())
-    }
-}
-
-struct Resolver<'a, 'db> {
-    scope: &'a hir::SemanticsScope<'db>,
-    hygiene: &'a hir::Hygiene,
-    placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
-}
-
-impl Resolver<'_, '_> {
-    fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
-        let mut resolved_paths = FxHashMap::default();
-        self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
-        let ufcs_function_calls = resolved_paths
-            .iter()
-            .filter_map(|(path_node, resolved)| {
-                if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
-                    if grandparent.kind() == SyntaxKind::CALL_EXPR {
-                        if let hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) =
-                            &resolved.resolution
-                        {
-                            return Some((grandparent, *function));
-                        }
-                    }
-                }
-                None
-            })
-            .collect();
-        Ok(ResolvedPattern {
-            node: pattern,
-            resolved_paths,
-            placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
-            ufcs_function_calls,
-        })
-    }
-
-    fn resolve(
-        &self,
-        node: SyntaxNode,
-        depth: u32,
-        resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
-    ) -> Result<(), SsrError> {
-        use ra_syntax::ast::AstNode;
-        if let Some(path) = ast::Path::cast(node.clone()) {
-            // Check if this is an appropriate place in the path to resolve. If the path is
-            // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
-            // a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
-            if !path_contains_type_arguments(path.qualifier())
-                && !self.path_contains_placeholder(&path)
-            {
-                let resolution = self
-                    .resolve_path(&path)
-                    .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
-                resolved_paths.insert(node, ResolvedPath { resolution, depth });
-                return Ok(());
-            }
-        }
-        for node in node.children() {
-            self.resolve(node, depth + 1, resolved_paths)?;
-        }
-        Ok(())
-    }
-
-    /// Returns whether `path` contains a placeholder, but ignores any placeholders within type
-    /// arguments.
-    fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
-        if let Some(segment) = path.segment() {
-            if let Some(name_ref) = segment.name_ref() {
-                if self.placeholders_by_stand_in.contains_key(name_ref.text()) {
-                    return true;
-                }
-            }
-        }
-        if let Some(qualifier) = path.qualifier() {
-            return self.path_contains_placeholder(&qualifier);
-        }
-        false
-    }
-
-    fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
-        let hir_path = hir::Path::from_src(path.clone(), self.hygiene)?;
-        // First try resolving the whole path. This will work for things like
-        // `std::collections::HashMap`, but will fail for things like
-        // `std::collections::HashMap::new`.
-        if let Some(resolution) = self.scope.resolve_hir_path(&hir_path) {
-            return Some(resolution);
-        }
-        // Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
-        // that succeeds, then iterate through the candidates on the resolved type with the provided
-        // name.
-        let resolved_qualifier = self.scope.resolve_hir_path_qualifier(&hir_path.qualifier()?)?;
-        if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
-            adt.ty(self.scope.db).iterate_path_candidates(
-                self.scope.db,
-                self.scope.module()?.krate(),
-                &FxHashSet::default(),
-                Some(hir_path.segments().last()?.name),
-                |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
-            )
-        } else {
-            None
-        }
-    }
-}
-
-/// Returns whether `path` or any of its qualifiers contains type arguments.
-fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
-    if let Some(path) = path {
-        if let Some(segment) = path.segment() {
-            if segment.type_arg_list().is_some() {
-                mark::hit!(type_arguments_within_path);
-                return true;
-            }
-        }
-        return path_contains_type_arguments(path.qualifier());
-    }
-    false
-}
diff --git a/crates/ra_ssr/src/search.rs b/crates/ra_ssr/src/search.rs
deleted file mode 100644
index bcf0f04689..0000000000
--- a/crates/ra_ssr/src/search.rs
+++ /dev/null
@@ -1,232 +0,0 @@
-//! Searching for matches.
-
-use crate::{
-    matching,
-    resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
-    Match, MatchFinder,
-};
-use ra_db::FileRange;
-use ra_ide_db::{
-    defs::Definition,
-    search::{Reference, SearchScope},
-};
-use ra_syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
-use test_utils::mark;
-
-/// A cache for the results of find_usages. This is for when we have multiple patterns that have the
-/// same path. e.g. if the pattern was `foo::Bar` that can parse as a path, an expression, a type
-/// and as a pattern. In each, the usages of `foo::Bar` are the same and we'd like to avoid finding
-/// them more than once.
-#[derive(Default)]
-pub(crate) struct UsageCache {
-    usages: Vec<(Definition, Vec<Reference>)>,
-}
-
-impl<'db> MatchFinder<'db> {
-    /// Adds all matches for `rule` to `matches_out`. Matches may overlap in ways that make
-    /// replacement impossible, so further processing is required in order to properly nest matches
-    /// and remove overlapping matches. This is done in the `nesting` module.
-    pub(crate) fn find_matches_for_rule(
-        &self,
-        rule: &ResolvedRule,
-        usage_cache: &mut UsageCache,
-        matches_out: &mut Vec<Match>,
-    ) {
-        if pick_path_for_usages(&rule.pattern).is_none() {
-            self.slow_scan(rule, matches_out);
-            return;
-        }
-        self.find_matches_for_pattern_tree(rule, &rule.pattern, usage_cache, matches_out);
-    }
-
-    fn find_matches_for_pattern_tree(
-        &self,
-        rule: &ResolvedRule,
-        pattern: &ResolvedPattern,
-        usage_cache: &mut UsageCache,
-        matches_out: &mut Vec<Match>,
-    ) {
-        if let Some(resolved_path) = pick_path_for_usages(pattern) {
-            let definition: Definition = resolved_path.resolution.clone().into();
-            for reference in self.find_usages(usage_cache, definition) {
-                if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) {
-                    if !is_search_permitted_ancestors(&node_to_match) {
-                        mark::hit!(use_declaration_with_braces);
-                        continue;
-                    }
-                    if let Ok(m) =
-                        matching::get_match(false, rule, &node_to_match, &None, &self.sema)
-                    {
-                        matches_out.push(m);
-                    }
-                }
-            }
-        }
-    }
-
-    fn find_node_to_match(
-        &self,
-        resolved_path: &ResolvedPath,
-        reference: &Reference,
-    ) -> Option<SyntaxNode> {
-        let file = self.sema.parse(reference.file_range.file_id);
-        let depth = resolved_path.depth as usize;
-        let offset = reference.file_range.range.start();
-        if let Some(path) =
-            self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
-        {
-            self.sema.ancestors_with_macros(path.syntax().clone()).skip(depth).next()
-        } else if let Some(path) =
-            self.sema.find_node_at_offset_with_descend::<ast::MethodCallExpr>(file.syntax(), offset)
-        {
-            // If the pattern contained a path and we found a reference to that path that wasn't
-            // itself a path, but was a method call, then we need to adjust how far up to try
-            // matching by how deep the path was within a CallExpr. The structure would have been
-            // CallExpr, PathExpr, Path - i.e. a depth offset of 2. We don't need to check if the
-            // path was part of a CallExpr because if it wasn't then all that will happen is we'll
-            // fail to match, which is the desired behavior.
-            const PATH_DEPTH_IN_CALL_EXPR: usize = 2;
-            if depth < PATH_DEPTH_IN_CALL_EXPR {
-                return None;
-            }
-            self.sema
-                .ancestors_with_macros(path.syntax().clone())
-                .skip(depth - PATH_DEPTH_IN_CALL_EXPR)
-                .next()
-        } else {
-            None
-        }
-    }
-
-    fn find_usages<'a>(
-        &self,
-        usage_cache: &'a mut UsageCache,
-        definition: Definition,
-    ) -> &'a [Reference] {
-        // Logically if a lookup succeeds we should just return it. Unfortunately returning it would
-        // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a
-        // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two
-        // lookups in the case of a cache hit.
-        if usage_cache.find(&definition).is_none() {
-            let usages = definition.find_usages(&self.sema, Some(self.search_scope()));
-            usage_cache.usages.push((definition, usages));
-            return &usage_cache.usages.last().unwrap().1;
-        }
-        usage_cache.find(&definition).unwrap()
-    }
-
-    /// Returns the scope within which we want to search. We don't want un unrestricted search
-    /// scope, since we don't want to find references in external dependencies.
-    fn search_scope(&self) -> SearchScope {
-        // FIXME: We should ideally have a test that checks that we edit local roots and not library
-        // roots. This probably would require some changes to fixtures, since currently everything
-        // seems to get put into a single source root.
-        use ra_db::SourceDatabaseExt;
-        use ra_ide_db::symbol_index::SymbolsDatabase;
-        let mut files = Vec::new();
-        for &root in self.sema.db.local_roots().iter() {
-            let sr = self.sema.db.source_root(root);
-            files.extend(sr.iter());
-        }
-        SearchScope::files(&files)
-    }
-
-    fn slow_scan(&self, rule: &ResolvedRule, matches_out: &mut Vec<Match>) {
-        use ra_db::SourceDatabaseExt;
-        use ra_ide_db::symbol_index::SymbolsDatabase;
-        for &root in self.sema.db.local_roots().iter() {
-            let sr = self.sema.db.source_root(root);
-            for file_id in sr.iter() {
-                let file = self.sema.parse(file_id);
-                let code = file.syntax();
-                self.slow_scan_node(code, rule, &None, matches_out);
-            }
-        }
-    }
-
-    fn slow_scan_node(
-        &self,
-        code: &SyntaxNode,
-        rule: &ResolvedRule,
-        restrict_range: &Option<FileRange>,
-        matches_out: &mut Vec<Match>,
-    ) {
-        if !is_search_permitted(code) {
-            return;
-        }
-        if let Ok(m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) {
-            matches_out.push(m);
-        }
-        // If we've got a macro call, we already tried matching it pre-expansion, which is the only
-        // way to match the whole macro, now try expanding it and matching the expansion.
-        if let Some(macro_call) = ast::MacroCall::cast(code.clone()) {
-            if let Some(expanded) = self.sema.expand(&macro_call) {
-                if let Some(tt) = macro_call.token_tree() {
-                    // When matching within a macro expansion, we only want to allow matches of
-                    // nodes that originated entirely from within the token tree of the macro call.
-                    // i.e. we don't want to match something that came from the macro itself.
-                    self.slow_scan_node(
-                        &expanded,
-                        rule,
-                        &Some(self.sema.original_range(tt.syntax())),
-                        matches_out,
-                    );
-                }
-            }
-        }
-        for child in code.children() {
-            self.slow_scan_node(&child, rule, restrict_range, matches_out);
-        }
-    }
-}
-
-/// Returns whether we support matching within `node` and all of its ancestors.
-fn is_search_permitted_ancestors(node: &SyntaxNode) -> bool {
-    if let Some(parent) = node.parent() {
-        if !is_search_permitted_ancestors(&parent) {
-            return false;
-        }
-    }
-    is_search_permitted(node)
-}
-
-/// Returns whether we support matching within this kind of node.
-fn is_search_permitted(node: &SyntaxNode) -> bool {
-    // FIXME: Properly handle use declarations. At the moment, if our search pattern is `foo::bar`
-    // and the code is `use foo::{baz, bar}`, we'll match `bar`, since it resolves to `foo::bar`.
-    // However we'll then replace just the part we matched `bar`. We probably need to instead remove
-    // `bar` and insert a new use declaration.
-    node.kind() != SyntaxKind::USE_ITEM
-}
-
-impl UsageCache {
-    fn find(&mut self, definition: &Definition) -> Option<&[Reference]> {
-        // We expect a very small number of cache entries (generally 1), so a linear scan should be
-        // fast enough and avoids the need to implement Hash for Definition.
-        for (d, refs) in &self.usages {
-            if d == definition {
-                return Some(refs);
-            }
-        }
-        None
-    }
-}
-
-/// Returns a path that's suitable for path resolution. We exclude builtin types, since they aren't
-/// something that we can find references to. We then somewhat arbitrarily pick the path that is the
-/// longest as this is hopefully more likely to be less common, making it faster to find.
-fn pick_path_for_usages(pattern: &ResolvedPattern) -> Option<&ResolvedPath> {
-    // FIXME: Take the scope of the resolved path into account. e.g. if there are any paths that are
-    // private to the current module, then we definitely would want to pick them over say a path
-    // from std. Possibly we should go further than this and intersect the search scopes for all
-    // resolved paths then search only in that scope.
-    pattern
-        .resolved_paths
-        .iter()
-        .filter(|(_, p)| {
-            !matches!(p.resolution, hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)))
-        })
-        .map(|(node, resolved)| (node.text().len(), resolved))
-        .max_by(|(a, _), (b, _)| a.cmp(b))
-        .map(|(_, resolved)| resolved)
-}
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index a9b9c8923b..753001949e 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -74,10 +74,3 @@ fn read_stdin() -> Result<String> {
     std::io::stdin().read_to_string(&mut buff)?;
     Ok(buff)
 }
-
-fn report_metric(metric: &str, value: u64, unit: &str) {
-    if std::env::var("RA_METRICS").is_err() {
-        return;
-    }
-    println!("METRIC:{}:{}:{}", metric, value, unit)
-}
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 076184ad6e..9299879b75 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -70,7 +70,7 @@ pub fn analysis_bench(
     match &what {
         BenchWhat::Highlight { .. } => {
             let res = do_work(&mut host, file_id, |analysis| {
-                analysis.diagnostics(file_id, true).unwrap();
+                analysis.diagnostics(file_id).unwrap();
                 analysis.highlight_as_html(file_id, false).unwrap()
             });
             if verbosity.is_verbose() {
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 10327ebb93..ddb3db6c3f 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -3,27 +3,26 @@
 
 use std::{path::Path, time::Instant};
 
+use itertools::Itertools;
+use rand::{seq::SliceRandom, thread_rng};
+use rayon::prelude::*;
+use rustc_hash::FxHashSet;
+
 use hir::{
     db::{AstDatabase, DefDatabase, HirDatabase},
     original_range, AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
 };
 use hir_def::FunctionId;
 use hir_ty::{Ty, TypeWalk};
-use itertools::Itertools;
 use ra_db::{
     salsa::{self, ParallelDatabase},
     SourceDatabaseExt,
 };
 use ra_syntax::AstNode;
-use rand::{seq::SliceRandom, thread_rng};
-use rayon::prelude::*;
-use rustc_hash::FxHashSet;
 use stdx::format_to;
 
 use crate::{
-    cli::{
-        load_cargo::load_cargo, progress_report::ProgressReport, report_metric, Result, Verbosity,
-    },
+    cli::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity},
     print_memory_usage,
 };
 
@@ -49,7 +48,7 @@ pub fn analysis_stats(
     let db_load_time = Instant::now();
     let (host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
     let db = host.raw_database();
-    eprintln!("Database loaded {:?}", db_load_time.elapsed());
+    println!("Database loaded {:?}", db_load_time.elapsed());
     let analysis_time = Instant::now();
     let mut num_crates = 0;
     let mut visited_modules = FxHashSet::default();
@@ -75,7 +74,7 @@ pub fn analysis_stats(
         visit_queue.shuffle(&mut thread_rng());
     }
 
-    eprintln!("Crates in this dir: {}", num_crates);
+    println!("Crates in this dir: {}", num_crates);
     let mut num_decls = 0;
     let mut funcs = Vec::new();
     while let Some(module) = visit_queue.pop() {
@@ -99,15 +98,10 @@ pub fn analysis_stats(
             }
         }
     }
-    eprintln!("Total modules found: {}", visited_modules.len());
-    eprintln!("Total declarations: {}", num_decls);
-    eprintln!("Total functions: {}", funcs.len());
-    let item_collection_memory = ra_prof::memory_usage();
-    eprintln!(
-        "Item Collection: {:?}, {}",
-        analysis_time.elapsed(),
-        item_collection_memory.allocated
-    );
+    println!("Total modules found: {}", visited_modules.len());
+    println!("Total declarations: {}", num_decls);
+    println!("Total functions: {}", funcs.len());
+    println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
 
     if randomize {
         funcs.shuffle(&mut thread_rng());
@@ -129,11 +123,7 @@ pub fn analysis_stats(
                 snap.0.infer(f_id.into());
             })
             .count();
-        eprintln!(
-            "Parallel Inference: {:?}, {}",
-            inference_time.elapsed(),
-            ra_prof::memory_usage().allocated
-        );
+        println!("Parallel Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
     }
 
     let inference_time = Instant::now();
@@ -270,31 +260,20 @@ pub fn analysis_stats(
         bar.inc(1);
     }
     bar.finish_and_clear();
-    eprintln!("Total expressions: {}", num_exprs);
-    eprintln!(
+    println!("Total expressions: {}", num_exprs);
+    println!(
         "Expressions of unknown type: {} ({}%)",
         num_exprs_unknown,
         if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
     );
-    eprintln!(
+    println!(
         "Expressions of partially unknown type: {} ({}%)",
         num_exprs_partially_unknown,
         if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
     );
-    eprintln!("Type mismatches: {}", num_type_mismatches);
-
-    let inference_time = inference_time.elapsed();
-    let total_memory = ra_prof::memory_usage();
-    eprintln!(
-        "Inference: {:?}, {}",
-        inference_time,
-        total_memory.allocated - item_collection_memory.allocated
-    );
-
-    let analysis_time = analysis_time.elapsed();
-    eprintln!("Total: {:?}, {}", analysis_time, total_memory);
-    report_metric("total time", analysis_time.as_millis() as u64, "ms");
-    report_metric("total memory", total_memory.allocated.megabytes() as u64, "MB");
+    println!("Type mismatches: {}", num_type_mismatches);
+    println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
+    println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
 
     if memory_usage {
         print_memory_usage(host, vfs);
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 4ac8c8772e..6f3c1c1f96 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -47,7 +47,7 @@ pub fn diagnostics(
                 String::from("unknown")
             };
             println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
-            for diagnostic in analysis.diagnostics(file_id, true).unwrap() {
+            for diagnostic in analysis.diagnostics(file_id).unwrap() {
                 if matches!(diagnostic.severity, Severity::Error) {
                     found_error = true;
                 }
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 194bec008d..4fb829ea5c 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -1,17 +1,27 @@
 //! Applies structured search replace rules from the command line.
 
 use crate::cli::{load_cargo::load_cargo, Result};
+use ra_ide::SourceFileEdit;
 use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
 
 pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
     use ra_db::SourceDatabaseExt;
+    use ra_ide_db::symbol_index::SymbolsDatabase;
     let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
     let db = host.raw_database();
-    let mut match_finder = MatchFinder::at_first_file(db)?;
+    let mut match_finder = MatchFinder::new(db);
     for rule in rules {
-        match_finder.add_rule(rule)?;
+        match_finder.add_rule(rule);
+    }
+    let mut edits = Vec::new();
+    for &root in db.local_roots().iter() {
+        let sr = db.source_root(root);
+        for file_id in sr.iter() {
+            if let Some(edit) = match_finder.edits_for_file(file_id) {
+                edits.push(SourceFileEdit { file_id, edit });
+            }
+        }
     }
-    let edits = match_finder.edits();
     for edit in edits {
         if let Some(path) = vfs.file_path(edit.file_id).as_path() {
             let mut contents = db.file_text(edit.file_id).to_string();
@@ -28,27 +38,34 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
 pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
     use ra_db::SourceDatabaseExt;
     use ra_ide_db::symbol_index::SymbolsDatabase;
-    let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
+    let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
     let db = host.raw_database();
-    let mut match_finder = MatchFinder::at_first_file(db)?;
+    let mut match_finder = MatchFinder::new(db);
     for pattern in patterns {
-        match_finder.add_search_pattern(pattern)?;
+        match_finder.add_search_pattern(pattern);
     }
-    if let Some(debug_snippet) = &debug_snippet {
-        for &root in db.local_roots().iter() {
-            let sr = db.source_root(root);
-            for file_id in sr.iter() {
+    for &root in db.local_roots().iter() {
+        let sr = db.source_root(root);
+        for file_id in sr.iter() {
+            if let Some(debug_snippet) = &debug_snippet {
                 for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
                     println!("{:#?}", debug_info);
                 }
+            } else {
+                let matches = match_finder.find_matches_in_file(file_id);
+                if !matches.matches.is_empty() {
+                    let matches = matches.flattened().matches;
+                    if let Some(path) = vfs.file_path(file_id).as_path() {
+                        println!("{} matches in '{}'", matches.len(), path.to_string_lossy());
+                    }
+                    // We could possibly at some point do something more useful than just printing
+                    // the matched text. For now though, that's the easiest thing to do.
+                    for m in matches {
+                        println!("{}", m.matched_text());
+                    }
+                }
             }
         }
-    } else {
-        for m in match_finder.matches().flattened().matches {
-            // We could possibly at some point do something more useful than just printing
-            // the matched text. For now though, that's the easiest thing to do.
-            println!("{}", m.matched_text());
-        }
     }
     Ok(())
 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e11c8b909a..8947ccf07e 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -23,7 +23,6 @@ pub struct Config {
     pub client_caps: ClientCapsConfig,
 
     pub publish_diagnostics: bool,
-    pub experimental_diagnostics: bool,
     pub diagnostics: DiagnosticsConfig,
     pub lru_capacity: Option<usize>,
     pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
@@ -138,7 +137,6 @@ impl Config {
 
             with_sysroot: true,
             publish_diagnostics: true,
-            experimental_diagnostics: true,
             diagnostics: DiagnosticsConfig::default(),
             lru_capacity: None,
             proc_macro_srv: None,
@@ -189,7 +187,6 @@ impl Config {
 
         self.with_sysroot = data.withSysroot;
         self.publish_diagnostics = data.diagnostics_enable;
-        self.experimental_diagnostics = data.diagnostics_enableExperimental;
         self.diagnostics = DiagnosticsConfig {
             warnings_as_info: data.diagnostics_warningsAsInfo,
             warnings_as_hint: data.diagnostics_warningsAsHint,
@@ -408,7 +405,6 @@ config_data! {
         completion_postfix_enable: bool          = true,
 
         diagnostics_enable: bool                = true,
-        diagnostics_enableExperimental: bool    = true,
         diagnostics_warningsAsHint: Vec<String> = Vec::new(),
         diagnostics_warningsAsInfo: Vec<String> = Vec::new(),
 
diff --git a/crates/rust-analyzer/src/document.rs b/crates/rust-analyzer/src/document.rs
deleted file mode 100644
index 43219e6330..0000000000
--- a/crates/rust-analyzer/src/document.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-//! In-memory document information.
-
-/// Information about a document that the Language Client
-// knows about.
-// Its lifetime is driven by the textDocument/didOpen and textDocument/didClose
-// client notifications.
-#[derive(Debug, Clone)]
-pub(crate) struct DocumentData {
-    pub version: Option<i64>,
-}
-
-impl DocumentData {
-    pub fn new(version: i64) -> Self {
-        DocumentData { version: Some(version) }
-    }
-}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index b2d65a6d18..80937dbc4d 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -17,7 +17,6 @@ use rustc_hash::FxHashMap;
 use crate::{
     config::Config,
     diagnostics::{CheckFixes, DiagnosticCollection},
-    document::DocumentData,
     from_proto,
     line_endings::LineEndings,
     main_loop::Task,
@@ -70,7 +69,7 @@ pub(crate) struct GlobalState {
     pub(crate) config: Config,
     pub(crate) analysis_host: AnalysisHost,
     pub(crate) diagnostics: DiagnosticCollection,
-    pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
+    pub(crate) mem_docs: FxHashMap<VfsPath, Option<i64>>,
     pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
     pub(crate) status: Status,
     pub(crate) source_root_config: SourceRootConfig,
@@ -85,7 +84,7 @@ pub(crate) struct GlobalStateSnapshot {
     pub(crate) analysis: Analysis,
     pub(crate) check_fixes: CheckFixes,
     pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
-    mem_docs: FxHashMap<VfsPath, DocumentData>,
+    mem_docs: FxHashMap<VfsPath, Option<i64>>,
     vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
     pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
 }
@@ -260,7 +259,7 @@ impl GlobalStateSnapshot {
 
     pub(crate) fn url_file_version(&self, url: &Url) -> Option<i64> {
         let path = from_proto::vfs_path(&url).ok()?;
-        self.mem_docs.get(&path)?.version
+        self.mem_docs.get(&path).copied()?
     }
 
     pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index cd309ed744..8d8c9442b6 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -774,7 +774,7 @@ fn handle_fixes(
         None => {}
     };
 
-    let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?;
+    let diagnostics = snap.analysis.diagnostics(file_id)?;
 
     let fixes_from_diagnostics = diagnostics
         .into_iter()
@@ -1026,9 +1026,8 @@ pub(crate) fn handle_ssr(
     params: lsp_ext::SsrParams,
 ) -> Result<lsp_types::WorkspaceEdit> {
     let _p = profile("handle_ssr");
-    let position = from_proto::file_position(&snap, params.position)?;
     let source_change =
-        snap.analysis.structural_search_replace(&params.query, params.parse_only, position)??;
+        snap.analysis.structural_search_replace(&params.query, params.parse_only)??;
     to_proto::workspace_edit(&snap, source_change)
 }
 
@@ -1040,7 +1039,7 @@ pub(crate) fn publish_diagnostics(
     let line_index = snap.analysis.file_line_index(file_id)?;
     let diagnostics: Vec<Diagnostic> = snap
         .analysis
-        .diagnostics(file_id, snap.config.experimental_diagnostics)?
+        .diagnostics(file_id)?
         .into_iter()
         .map(|d| Diagnostic {
             range: to_proto::range(&line_index, d.range),
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index ed37992cd3..369830973a 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -33,7 +33,6 @@ mod line_endings;
 mod request_metrics;
 mod lsp_utils;
 mod thread_pool;
-mod document;
 pub mod lsp_ext;
 pub mod config;
 
@@ -86,6 +85,6 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
     mem.push(("Remaining".into(), ra_prof::memory_usage().allocated));
 
     for (name, bytes) in mem {
-        eprintln!("{:>8} {}", bytes, name);
+        println!("{:>8} {}", bytes, name);
     }
 }
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 113e0e070a..13ebb18fbb 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -216,11 +216,6 @@ impl Request for Ssr {
 pub struct SsrParams {
     pub query: String,
     pub parse_only: bool,
-
-    /// File position where SSR was invoked. Paths in `query` will be resolved relative to this
-    /// position.
-    #[serde(flatten)]
-    pub position: lsp_types::TextDocumentPositionParams,
 }
 
 pub enum StatusNotification {}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 51626fcd58..e95d4157c0 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -15,7 +15,6 @@ use ra_prof::profile;
 use crate::{
     config::Config,
     dispatch::{NotificationDispatcher, RequestDispatcher},
-    document::DocumentData,
     from_proto,
     global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
     handlers, lsp_ext,
@@ -312,7 +311,7 @@ impl GlobalState {
                 let url = file_id_to_url(&self.vfs.read().0, file_id);
                 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
                 let version = from_proto::vfs_path(&url)
-                    .map(|path| self.mem_docs.get(&path)?.version)
+                    .map(|path| self.mem_docs.get(&path).copied().flatten())
                     .unwrap_or_default();
 
                 self.send_notification::<lsp_types::notification::PublishDiagnostics>(
@@ -407,7 +406,7 @@ impl GlobalState {
                 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
                     if this
                         .mem_docs
-                        .insert(path.clone(), DocumentData::new(params.text_document.version))
+                        .insert(path.clone(), Some(params.text_document.version))
                         .is_some()
                     {
                         log::error!("duplicate DidOpenTextDocument: {}", path)
@@ -429,7 +428,7 @@ impl GlobalState {
 
                     // The version passed in DidChangeTextDocument is the version after all edits are applied
                     // so we should apply it before the vfs is notified.
-                    doc.version = params.text_document.version;
+                    *doc = params.text_document.version;
 
                     vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
                 }
@@ -439,7 +438,7 @@ impl GlobalState {
                 let mut version = None;
                 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
                     match this.mem_docs.remove(&path) {
-                        Some(doc) => version = doc.version,
+                        Some(entry) => version = entry,
                         None => log::error!("orphan DidCloseTextDocument: {}", path),
                     }
 
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 1be01fd884..98d14450b1 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -274,11 +274,6 @@ interface SsrParams {
     query: string,
     /// If true, only check the syntax of the query and don't compute the actual edit.
     parseOnly: bool,
-    /// The current text document. This and `position` will be used to determine in what scope
-    /// paths in `query` should be resolved.
-    textDocument: lc.TextDocumentIdentifier;
-    /// Position where SSR was invoked.
-    position: lc.Position;
 }
 ```
 
@@ -290,7 +285,7 @@ WorkspaceEdit
 
 ### Example
 
-SSR with query `foo($a, $b) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
+SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
 
 ### Unresolved Question
 
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index cfc0ad81cf..4b31145deb 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -300,7 +300,7 @@ interface Crate {
     /// `include_dirs` are included recursively, unless a subdirectory is in `exclude_dirs`.
     ///
     /// Different crates can share the same `source`.
-    ///
+
     /// If two crates share an `.rs` file in common, they *must* have the same `source`.
     /// rust-analyzer assumes that files from one source can't refer to files in another source.
     source?: {
diff --git a/editors/code/package.json b/editors/code/package.json
index 658c913fdb..448e2269ff 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -200,6 +200,11 @@
             "type": "object",
             "title": "Rust Analyzer",
             "properties": {
+                "rust-analyzer.diagnostics.enable": {
+                    "type": "boolean",
+                    "default": true,
+                    "markdownDescription": "Whether to show native rust-analyzer diagnostics."
+                },
                 "rust-analyzer.lruCapacity": {
                     "type": [
                         "null",
@@ -574,16 +579,6 @@
                     "type": "boolean",
                     "default": true
                 },
-                "rust-analyzer.diagnostics.enable": {
-                    "type": "boolean",
-                    "default": true,
-                    "markdownDescription": "Whether to show native rust-analyzer diagnostics."
-                },
-                "rust-analyzer.diagnostics.enableExperimental": {
-                    "type": "boolean",
-                    "default": true,
-                    "markdownDescription": "Whether to show experimental rust-analyzer diagnostics that might have more false positives than usual."
-                },
                 "rust-analyzer.diagnostics.warningsAsInfo": {
                     "type": "array",
                     "uniqueItems": true,
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index c21e5597cb..1f3a7cf7e8 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -185,21 +185,15 @@ export function parentModule(ctx: Ctx): Cmd {
 
 export function ssr(ctx: Ctx): Cmd {
     return async () => {
-        const editor = vscode.window.activeTextEditor;
         const client = ctx.client;
-        if (!editor || !client) return;
-
-        const position = editor.selection.active;
-        const textDocument = { uri: editor.document.uri.toString() };
+        if (!client) return;
 
         const options: vscode.InputBoxOptions = {
             value: "() ==>> ()",
             prompt: "Enter request, for example 'Foo($a) ==> Foo::new($a)' ",
             validateInput: async (x: string) => {
                 try {
-                    await client.sendRequest(ra.ssr, {
-                        query: x, parseOnly: true, textDocument, position,
-                    });
+                    await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
                 } catch (e) {
                     return e.toString();
                 }
@@ -214,9 +208,7 @@ export function ssr(ctx: Ctx): Cmd {
             title: "Structured search replace in progress...",
             cancellable: false,
         }, async (_progress, _token) => {
-            const edit = await client.sendRequest(ra.ssr, {
-                query: request, parseOnly: false, textDocument, position
-            });
+            const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
 
             await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
         });
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index bd92c5b6d7..925126a16f 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -87,9 +87,17 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
         debugOutput.show(true);
     }
 
-    const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active.
+    const isMultiFolderWorkspace = vscode.workspace.workspaceFolders!.length > 1;
+    const firstWorkspace = vscode.workspace.workspaceFolders![0]; // folder exists or RA is not active.
+    const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
+        firstWorkspace :
+        vscode.workspace.workspaceFolders!.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
+
+    const wsFolder = path.normalize(workspace.uri.fsPath);
+    const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
     function simplifyPath(p: string): string {
-        return path.normalize(p).replace(wsFolder, '${workspaceRoot}');
+        // see https://github.com/rust-analyzer/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
+        return path.normalize(p).replace(wsFolder, '${workspaceFolder' + workspaceQualifier + '}');
     }
 
     const executable = await getDebugExecutable(runnable);
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 149f9a0d64..5f32cb40e9 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -93,8 +93,6 @@ export const inlayHints = new lc.RequestType<InlayHintsParams, InlayHint[], void
 export interface SsrParams {
     query: string;
     parseOnly: boolean;
-    textDocument: lc.TextDocumentIdentifier;
-    position: lc.Position;
 }
 export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
 
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index 01d903cdee..c198c09077 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -13,24 +13,17 @@ use crate::{
     project_root,
 };
 
-pub struct DistCmd {
-    pub nightly: bool,
-    pub client_version: Option<String>,
-}
-
-impl DistCmd {
-    pub fn run(self) -> Result<()> {
-        let dist = project_root().join("dist");
-        rm_rf(&dist)?;
-        fs2::create_dir_all(&dist)?;
-
-        if let Some(version) = self.client_version {
-            let release_tag = if self.nightly { "nightly".to_string() } else { date_iso()? };
-            dist_client(&version, &release_tag)?;
-        }
-        dist_server()?;
-        Ok(())
+pub fn run_dist(nightly: bool, client_version: Option<String>) -> Result<()> {
+    let dist = project_root().join("dist");
+    rm_rf(&dist)?;
+    fs2::create_dir_all(&dist)?;
+
+    if let Some(version) = client_version {
+        let release_tag = if nightly { "nightly".to_string() } else { date_iso()? };
+        dist_client(&version, &release_tag)?;
     }
+    dist_server()?;
+    Ok(())
 }
 
 fn dist_client(version: &str, release_tag: &str) -> Result<()> {
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 2fdb08f2e1..94d451e231 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -7,7 +7,6 @@ pub mod install;
 pub mod release;
 pub mod dist;
 pub mod pre_commit;
-pub mod metrics;
 
 pub mod codegen;
 mod ast_src;
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index b69b884e54..53d3ce3e72 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -13,9 +13,8 @@ use std::env;
 use pico_args::Arguments;
 use xtask::{
     codegen::{self, Mode},
-    dist::DistCmd,
+    dist::run_dist,
     install::{ClientOpt, InstallCmd, Malloc, ServerOpt},
-    metrics::MetricsCmd,
     not_bash::pushd,
     pre_commit, project_root,
     release::{PromoteCmd, ReleaseCmd},
@@ -116,12 +115,7 @@ FLAGS:
             let nightly = args.contains("--nightly");
             let client_version: Option<String> = args.opt_value_from_str("--client")?;
             args.finish()?;
-            DistCmd { nightly, client_version }.run()
-        }
-        "metrics" => {
-            let dry_run = args.contains("--dry-run");
-            args.finish()?;
-            MetricsCmd { dry_run }.run()
+            run_dist(nightly, client_version)
         }
         _ => {
             eprintln!(
@@ -139,8 +133,7 @@ SUBCOMMANDS:
     codegen
     install
     lint
-    dist
-    promote"
+    dist"
             );
             Ok(())
         }
diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs
deleted file mode 100644
index 9ac3fa51d0..0000000000
--- a/xtask/src/metrics.rs
+++ /dev/null
@@ -1,279 +0,0 @@
-use std::{
-    collections::BTreeMap,
-    env,
-    fmt::{self, Write as _},
-    io::Write as _,
-    path::Path,
-    time::{Instant, SystemTime, UNIX_EPOCH},
-};
-
-use anyhow::{bail, format_err, Result};
-
-use crate::not_bash::{fs2, pushd, pushenv, rm_rf, run};
-
-type Unit = String;
-
-pub struct MetricsCmd {
-    pub dry_run: bool,
-}
-
-impl MetricsCmd {
-    pub fn run(self) -> Result<()> {
-        let mut metrics = Metrics::new()?;
-        if !self.dry_run {
-            rm_rf("./target/release")?;
-        }
-        if !Path::new("./target/rustc-perf").exists() {
-            fs2::create_dir_all("./target/rustc-perf")?;
-            run!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")?;
-        }
-        {
-            let _d = pushd("./target/rustc-perf");
-            run!("git reset --hard 1d9288b0da7febf2599917da1b57dc241a1af033")?;
-        }
-
-        let _env = pushenv("RA_METRICS", "1");
-
-        metrics.measure_build()?;
-        metrics.measure_analysis_stats_self()?;
-        metrics.measure_analysis_stats("ripgrep")?;
-        metrics.measure_analysis_stats("webrender")?;
-
-        if !self.dry_run {
-            let _d = pushd("target");
-            let metrics_token = env::var("METRICS_TOKEN").unwrap();
-            let repo = format!("https://{}@github.com/rust-analyzer/metrics.git", metrics_token);
-            run!("git clone --depth 1 {}", repo)?;
-            let _d = pushd("metrics");
-
-            let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
-            writeln!(file, "{}", metrics.json())?;
-            run!("git add .")?;
-            run!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")?;
-            run!("git push origin master")?;
-        }
-        eprintln!("{:#?}", metrics);
-        Ok(())
-    }
-}
-
-impl Metrics {
-    fn measure_build(&mut self) -> Result<()> {
-        eprintln!("\nMeasuring build");
-        run!("cargo fetch")?;
-
-        let time = Instant::now();
-        run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?;
-        let time = time.elapsed();
-        self.report("build", time.as_millis() as u64, "ms".into());
-        Ok(())
-    }
-    fn measure_analysis_stats_self(&mut self) -> Result<()> {
-        self.measure_analysis_stats_path("self", &".")
-    }
-    fn measure_analysis_stats(&mut self, bench: &str) -> Result<()> {
-        self.measure_analysis_stats_path(
-            bench,
-            &format!("./target/rustc-perf/collector/benchmarks/{}", bench),
-        )
-    }
-    fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> {
-        eprintln!("\nMeasuring analysis-stats/{}", name);
-        let output = run!("./target/release/rust-analyzer analysis-stats --quiet {}", path)?;
-        for (metric, value, unit) in parse_metrics(&output) {
-            self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into());
-        }
-        Ok(())
-    }
-}
-
-fn parse_metrics(output: &str) -> Vec<(&str, u64, &str)> {
-    output
-        .lines()
-        .filter_map(|it| {
-            let entry = it.split(':').collect::<Vec<_>>();
-            match entry.as_slice() {
-                ["METRIC", name, value, unit] => Some((*name, value.parse().unwrap(), *unit)),
-                _ => None,
-            }
-        })
-        .collect()
-}
-
-#[derive(Debug)]
-struct Metrics {
-    host: Host,
-    timestamp: SystemTime,
-    revision: String,
-    metrics: BTreeMap<String, (u64, Unit)>,
-}
-
-#[derive(Debug)]
-struct Host {
-    os: String,
-    cpu: String,
-    mem: String,
-}
-
-impl Metrics {
-    fn new() -> Result<Metrics> {
-        let host = Host::new()?;
-        let timestamp = SystemTime::now();
-        let revision = run!("git rev-parse HEAD")?;
-        Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() })
-    }
-
-    fn report(&mut self, name: &str, value: u64, unit: Unit) {
-        self.metrics.insert(name.into(), (value, unit));
-    }
-
-    fn json(&self) -> Json {
-        let mut json = Json::default();
-        self.to_json(&mut json);
-        json
-    }
-    fn to_json(&self, json: &mut Json) {
-        json.begin_object();
-        {
-            json.field("host");
-            self.host.to_json(json);
-
-            json.field("timestamp");
-            let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
-            json.number(timestamp.as_secs() as f64);
-
-            json.field("revision");
-            json.string(&self.revision);
-
-            json.field("metrics");
-            json.begin_object();
-            {
-                for (k, (value, unit)) in &self.metrics {
-                    json.field(k);
-                    json.begin_array();
-                    {
-                        json.number(*value as f64);
-                        json.string(unit);
-                    }
-                    json.end_array();
-                }
-            }
-            json.end_object()
-        }
-        json.end_object();
-    }
-}
-
-impl Host {
-    fn new() -> Result<Host> {
-        if cfg!(not(target_os = "linux")) {
-            bail!("can only collect metrics on Linux ");
-        }
-
-        let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
-
-        let cpu =
-            read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();
-
-        let mem = read_field("/proc/meminfo", "MemTotal:")?;
-
-        return Ok(Host { os, cpu, mem });
-
-        fn read_field<'a>(path: &str, field: &str) -> Result<String> {
-            let text = fs2::read_to_string(path)?;
-
-            let line = text
-                .lines()
-                .find(|it| it.starts_with(field))
-                .ok_or_else(|| format_err!("can't parse {}", path))?;
-            Ok(line[field.len()..].trim().to_string())
-        }
-    }
-    fn to_json(&self, json: &mut Json) {
-        json.begin_object();
-        {
-            json.field("os");
-            json.string(&self.os);
-
-            json.field("cpu");
-            json.string(&self.cpu);
-
-            json.field("mem");
-            json.string(&self.mem);
-        }
-        json.end_object();
-    }
-}
-
-struct State {
-    obj: bool,
-    first: bool,
-}
-
-#[derive(Default)]
-struct Json {
-    stack: Vec<State>,
-    buf: String,
-}
-
-impl Json {
-    fn begin_object(&mut self) {
-        self.stack.push(State { obj: true, first: true });
-        self.buf.push('{');
-    }
-    fn end_object(&mut self) {
-        self.stack.pop();
-        self.buf.push('}')
-    }
-    fn begin_array(&mut self) {
-        self.stack.push(State { obj: false, first: true });
-        self.buf.push('[');
-    }
-    fn end_array(&mut self) {
-        self.stack.pop();
-        self.buf.push(']')
-    }
-    fn field(&mut self, name: &str) {
-        self.object_comma();
-        self.string_token(name);
-        self.buf.push(':');
-    }
-    fn string(&mut self, value: &str) {
-        self.array_comma();
-        self.string_token(value);
-    }
-    fn string_token(&mut self, value: &str) {
-        self.buf.push('"');
-        self.buf.extend(value.escape_default());
-        self.buf.push('"');
-    }
-    fn number(&mut self, value: f64) {
-        self.array_comma();
-        write!(self.buf, "{}", value).unwrap();
-    }
-
-    fn array_comma(&mut self) {
-        let state = self.stack.last_mut().unwrap();
-        if state.obj {
-            return;
-        }
-        if !state.first {
-            self.buf.push(',');
-        }
-        state.first = false;
-    }
-
-    fn object_comma(&mut self) {
-        let state = self.stack.last_mut().unwrap();
-        if !state.first {
-            self.buf.push(',');
-        }
-        state.first = false;
-    }
-}
-
-impl fmt::Display for Json {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}", self.buf)
-    }
-}
diff --git a/xtask/src/not_bash.rs b/xtask/src/not_bash.rs
index ef811e5bf3..0f3a56b25f 100644
--- a/xtask/src/not_bash.rs
+++ b/xtask/src/not_bash.rs
@@ -186,8 +186,7 @@ impl Env {
     fn pushd(&mut self, dir: PathBuf) {
         let dir = self.cwd().join(dir);
         self.pushd_stack.push(dir);
-        env::set_current_dir(self.cwd())
-            .unwrap_or_else(|err| panic!("Failed to set cwd to {}: {}", self.cwd().display(), err));
+        env::set_current_dir(self.cwd()).unwrap();
     }
     fn popd(&mut self) {
         self.pushd_stack.pop().unwrap();


Test patch:
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index b38807c0f9..f20ae2cdf9 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,8 +1,5 @@
 use crate::{MatchFinder, SsrRule};
-use expect::{expect, Expect};
-use ra_db::{salsa::Durability, FileId, FilePosition, SourceDatabaseExt};
-use rustc_hash::FxHashSet;
-use std::sync::Arc;
+use ra_db::{FileId, SourceDatabaseExt};
 use test_utils::mark;
 
 fn parse_error_text(query: &str) -> String {
@@ -39,7 +36,7 @@ fn parser_repeated_name() {
 fn parser_invalid_pattern() {
     assert_eq!(
         parse_error_text(" ==>> ()"),
-        "Parse error: Not a valid Rust expression, type, item, path or pattern"
+        "Parse error: Pattern is not a valid Rust expression, type, item, path or pattern"
     );
 }
 
@@ -47,7 +44,7 @@ fn parser_invalid_pattern() {
 fn parser_invalid_template() {
     assert_eq!(
         parse_error_text("() ==>> )"),
-        "Parse error: Not a valid Rust expression, type, item, path or pattern"
+        "Parse error: Replacement is not a valid Rust expression, type, item, path or pattern"
     );
 }
 
@@ -59,44 +56,39 @@ fn parser_undefined_placeholder_in_replacement() {
     );
 }
 
-/// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be
-/// the start of the file.
-pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FilePosition) {
+fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) {
     use ra_db::fixture::WithFixture;
-    use ra_ide_db::symbol_index::SymbolsDatabase;
-    let (mut db, position) = if code.contains(test_utils::CURSOR_MARKER) {
-        ra_ide_db::RootDatabase::with_position(code)
-    } else {
-        let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code);
-        (db, FilePosition { file_id, offset: 0.into() })
-    };
-    let mut local_roots = FxHashSet::default();
-    local_roots.insert(ra_db::fixture::WORKSPACE);
-    db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
-    (db, position)
+    ra_ide_db::RootDatabase::with_single_file(code)
+}
+
+fn assert_ssr_transform(rule: &str, input: &str, result: &str) {
+    assert_ssr_transforms(&[rule], input, result);
 }
 
-fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) {
-    assert_ssr_transforms(&[rule], input, expected);
+fn normalize_code(code: &str) -> String {
+    let (db, file_id) = single_file(code);
+    db.file_text(file_id).to_string()
 }
 
-fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
-    let (db, position) = single_file(input);
-    let mut match_finder = MatchFinder::in_context(&db, position);
+fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
+    let (db, file_id) = single_file(input);
+    let mut match_finder = MatchFinder::new(&db);
     for rule in rules {
         let rule: SsrRule = rule.parse().unwrap();
-        match_finder.add_rule(rule).unwrap();
+        match_finder.add_rule(rule);
     }
-    let edits = match_finder.edits();
-    if edits.is_empty() {
+    if let Some(edits) = match_finder.edits_for_file(file_id) {
+        // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
+        // stuff.
+        let mut after = db.file_text(file_id).to_string();
+        edits.apply(&mut after);
+        // Likewise, we need to make sure that whatever transformations fixture parsing applies,
+        // also get applied to our expected result.
+        let result = normalize_code(result);
+        assert_eq!(after, result);
+    } else {
         panic!("No edits were made");
     }
-    assert_eq!(edits[0].file_id, position.file_id);
-    // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
-    // stuff.
-    let mut actual = db.file_text(position.file_id).to_string();
-    edits[0].edit.apply(&mut actual);
-    expected.assert_eq(&actual);
 }
 
 fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) {
@@ -112,34 +104,39 @@ fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet:
 }
 
 fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
-    let (db, position) = single_file(code);
-    let mut match_finder = MatchFinder::in_context(&db, position);
-    match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
-    let matched_strings: Vec<String> =
-        match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect();
+    let (db, file_id) = single_file(code);
+    let mut match_finder = MatchFinder::new(&db);
+    match_finder.add_search_pattern(pattern.parse().unwrap());
+    let matched_strings: Vec<String> = match_finder
+        .find_matches_in_file(file_id)
+        .flattened()
+        .matches
+        .iter()
+        .map(|m| m.matched_text())
+        .collect();
     if matched_strings != expected && !expected.is_empty() {
-        print_match_debug_info(&match_finder, position.file_id, &expected[0]);
+        print_match_debug_info(&match_finder, file_id, &expected[0]);
     }
     assert_eq!(matched_strings, expected);
 }
 
 fn assert_no_match(pattern: &str, code: &str) {
-    let (db, position) = single_file(code);
-    let mut match_finder = MatchFinder::in_context(&db, position);
-    match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
-    let matches = match_finder.matches().flattened().matches;
+    let (db, file_id) = single_file(code);
+    let mut match_finder = MatchFinder::new(&db);
+    match_finder.add_search_pattern(pattern.parse().unwrap());
+    let matches = match_finder.find_matches_in_file(file_id).flattened().matches;
     if !matches.is_empty() {
-        print_match_debug_info(&match_finder, position.file_id, &matches[0].matched_text());
+        print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
         panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
     }
 }
 
 fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
-    let (db, position) = single_file(code);
-    let mut match_finder = MatchFinder::in_context(&db, position);
-    match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
+    let (db, file_id) = single_file(code);
+    let mut match_finder = MatchFinder::new(&db);
+    match_finder.add_search_pattern(pattern.parse().unwrap());
     let mut reasons = Vec::new();
-    for d in match_finder.debug_where_text_equal(position.file_id, snippet) {
+    for d in match_finder.debug_where_text_equal(file_id, snippet) {
         if let Some(reason) = d.match_failure_reason() {
             reasons.push(reason.to_owned());
         }
@@ -152,7 +149,7 @@ fn ssr_function_to_method() {
     assert_ssr_transform(
         "my_function($a, $b) ==>> ($a).my_method($b)",
         "fn my_function() {} fn main() { loop { my_function( other_func(x, y), z + w) } }",
-        expect![["fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }"]],
+        "fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }",
     )
 }
 
@@ -160,19 +157,8 @@ fn ssr_function_to_method() {
 fn ssr_nested_function() {
     assert_ssr_transform(
         "foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
-        r#"
-            //- /lib.rs crate:foo
-            fn foo() {}
-            fn bar() {}
-            fn baz() {}
-            fn main { foo  (x + value.method(b), x+y-z, true && false) }
-            "#,
-        expect![[r#"
-            fn foo() {}
-            fn bar() {}
-            fn baz() {}
-            fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }
-        "#]],
+        "fn foo() {} fn main { foo  (x + value.method(b), x+y-z, true && false) }",
+        "fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
     )
 }
 
@@ -181,7 +167,7 @@ fn ssr_expected_spacing() {
     assert_ssr_transform(
         "foo($x) + bar() ==>> bar($x)",
         "fn foo() {} fn bar() {} fn main() { foo(5) + bar() }",
-        expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
+        "fn foo() {} fn bar() {} fn main() { bar(5) }",
     );
 }
 
@@ -190,7 +176,7 @@ fn ssr_with_extra_space() {
     assert_ssr_transform(
         "foo($x  ) +    bar() ==>> bar($x)",
         "fn foo() {} fn bar() {} fn main() { foo(  5 )  +bar(   ) }",
-        expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
+        "fn foo() {} fn bar() {} fn main() { bar(5) }",
     );
 }
 
@@ -198,8 +184,8 @@ fn ssr_with_extra_space() {
 fn ssr_keeps_nested_comment() {
     assert_ssr_transform(
         "foo($x) ==>> bar($x)",
-        "fn foo() {} fn bar() {} fn main() { foo(other(5 /* using 5 */)) }",
-        expect![["fn foo() {} fn bar() {} fn main() { bar(other(5 /* using 5 */)) }"]],
+        "fn foo() {} fn main() { foo(other(5 /* using 5 */)) }",
+        "fn foo() {} fn main() { bar(other(5 /* using 5 */)) }",
     )
 }
 
@@ -207,25 +193,17 @@ fn ssr_keeps_nested_comment() {
 fn ssr_keeps_comment() {
     assert_ssr_transform(
         "foo($x) ==>> bar($x)",
-        "fn foo() {} fn bar() {} fn main() { foo(5 /* using 5 */) }",
-        expect![["fn foo() {} fn bar() {} fn main() { bar(5)/* using 5 */ }"]],
+        "fn foo() {} fn main() { foo(5 /* using 5 */) }",
+        "fn foo() {} fn main() { bar(5)/* using 5 */ }",
     )
 }
 
 #[test]
 fn ssr_struct_lit() {
     assert_ssr_transform(
-        "Foo{a: $a, b: $b} ==>> Foo::new($a, $b)",
-        r#"
-            struct Foo() {}
-            impl Foo { fn new() {} }
-            fn main() { Foo{b:2, a:1} }
-            "#,
-        expect![[r#"
-            struct Foo() {}
-            impl Foo { fn new() {} }
-            fn main() { Foo::new(1, 2) }
-        "#]],
+        "foo{a: $a, b: $b} ==>> foo::new($a, $b)",
+        "fn foo() {} fn main() { foo{b:2, a:1} }",
+        "fn foo() {} fn main() { foo::new(1, 2) }",
     )
 }
 
@@ -337,7 +315,7 @@ fn match_struct_instantiation() {
 fn match_path() {
     let code = r#"
         mod foo {
-            pub fn bar() {}
+            fn bar() {}
         }
         fn f() {foo::bar(42)}"#;
     assert_matches("foo::bar", code, &["foo::bar"]);
@@ -350,60 +328,6 @@ fn match_pattern() {
     assert_matches("Some($a)", "struct Some(); fn f() {if let Some(x) = foo() {}}", &["Some(x)"]);
 }
 
-// If our pattern has a full path, e.g. a::b::c() and the code has c(), but c resolves to
-// a::b::c, then we should match.
-#[test]
-fn match_fully_qualified_fn_path() {
-    let code = r#"
-        mod a {
-            pub mod b {
-                pub fn c(_: i32) {}
-            }
-        }
-        use a::b::c;
-        fn f1() {
-            c(42);
-        }
-        "#;
-    assert_matches("a::b::c($a)", code, &["c(42)"]);
-}
-
-#[test]
-fn match_resolved_type_name() {
-    let code = r#"
-        mod m1 {
-            pub mod m2 {
-                pub trait Foo<T> {}
-            }
-        }
-        mod m3 {
-            trait Foo<T> {}
-            fn f1(f: Option<&dyn Foo<bool>>) {}
-        }
-        mod m4 {
-            use crate::m1::m2::Foo;
-            fn f1(f: Option<&dyn Foo<i32>>) {}
-        }
-        "#;
-    assert_matches("m1::m2::Foo<$t>", code, &["Foo<i32>"]);
-}
-
-#[test]
-fn type_arguments_within_path() {
-    mark::check!(type_arguments_within_path);
-    let code = r#"
-        mod foo {
-            pub struct Bar<T> {t: T}
-            impl<T> Bar<T> {
-                pub fn baz() {}
-            }
-        }
-        fn f1() {foo::Bar::<i32>::baz();}
-        "#;
-    assert_no_match("foo::Bar::<i64>::baz()", code);
-    assert_matches("foo::Bar::<i32>::baz()", code, &["foo::Bar::<i32>::baz()"]);
-}
-
 #[test]
 fn literal_constraint() {
     mark::check!(literal_constraint);
@@ -492,8 +416,8 @@ fn no_match_split_expression() {
 fn replace_function_call() {
     assert_ssr_transform(
         "foo() ==>> bar()",
-        "fn foo() {} fn bar() {} fn f1() {foo(); foo();}",
-        expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]],
+        "fn foo() {} fn f1() {foo(); foo();}",
+        "fn foo() {} fn f1() {bar(); bar();}",
     );
 }
 
@@ -501,8 +425,8 @@ fn replace_function_call() {
 fn replace_function_call_with_placeholders() {
     assert_ssr_transform(
         "foo($a, $b) ==>> bar($b, $a)",
-        "fn foo() {} fn bar() {} fn f1() {foo(5, 42)}",
-        expect![["fn foo() {} fn bar() {} fn f1() {bar(42, 5)}"]],
+        "fn foo() {} fn f1() {foo(5, 42)}",
+        "fn foo() {} fn f1() {bar(42, 5)}",
     );
 }
 
@@ -510,120 +434,26 @@ fn replace_function_call_with_placeholders() {
 fn replace_nested_function_calls() {
     assert_ssr_transform(
         "foo($a) ==>> bar($a)",
-        "fn foo() {} fn bar() {} fn f1() {foo(foo(42))}",
-        expect![["fn foo() {} fn bar() {} fn f1() {bar(bar(42))}"]],
-    );
-}
-
-#[test]
-fn replace_associated_function_call() {
-    assert_ssr_transform(
-        "Foo::new() ==>> Bar::new()",
-        r#"
-            struct Foo {}
-            impl Foo { fn new() {} }
-            struct Bar {}
-            impl Bar { fn new() {} }
-            fn f1() {Foo::new();}
-            "#,
-        expect![[r#"
-            struct Foo {}
-            impl Foo { fn new() {} }
-            struct Bar {}
-            impl Bar { fn new() {} }
-            fn f1() {Bar::new();}
-        "#]],
-    );
-}
-
-#[test]
-fn replace_path_in_different_contexts() {
-    // Note the <|> inside module a::b which marks the point where the rule is interpreted. We
-    // replace foo with bar, but both need different path qualifiers in different contexts. In f4,
-    // foo is unqualified because of a use statement, however the replacement needs to be fully
-    // qualified.
-    assert_ssr_transform(
-        "c::foo() ==>> c::bar()",
-        r#"
-            mod a {
-                pub mod b {<|>
-                    pub mod c {
-                        pub fn foo() {}
-                        pub fn bar() {}
-                        fn f1() { foo() }
-                    }
-                    fn f2() { c::foo() }
-                }
-                fn f3() { b::c::foo() }
-            }
-            use a::b::c::foo;
-            fn f4() { foo() }
-            "#,
-        expect![[r#"
-            mod a {
-                pub mod b {
-                    pub mod c {
-                        pub fn foo() {}
-                        pub fn bar() {}
-                        fn f1() { bar() }
-                    }
-                    fn f2() { c::bar() }
-                }
-                fn f3() { b::c::bar() }
-            }
-            use a::b::c::foo;
-            fn f4() { a::b::c::bar() }
-            "#]],
+        "fn foo() {} fn f1() {foo(foo(42))}",
+        "fn foo() {} fn f1() {bar(bar(42))}",
     );
 }
 
 #[test]
-fn replace_associated_function_with_generics() {
+fn replace_type() {
     assert_ssr_transform(
-        "c::Foo::<$a>::new() ==>> d::Bar::<$a>::default()",
-        r#"
-            mod c {
-                pub struct Foo<T> {v: T}
-                impl<T> Foo<T> { pub fn new() {} }
-                fn f1() {
-                    Foo::<i32>::new();
-                }
-            }
-            mod d {
-                pub struct Bar<T> {v: T}
-                impl<T> Bar<T> { pub fn default() {} }
-                fn f1() {
-                    super::c::Foo::<i32>::new();
-                }
-            }
-            "#,
-        expect![[r#"
-            mod c {
-                pub struct Foo<T> {v: T}
-                impl<T> Foo<T> { pub fn new() {} }
-                fn f1() {
-                    crate::d::Bar::<i32>::default();
-                }
-            }
-            mod d {
-                pub struct Bar<T> {v: T}
-                impl<T> Bar<T> { pub fn default() {} }
-                fn f1() {
-                    Bar::<i32>::default();
-                }
-            }
-            "#]],
+        "Result<(), $a> ==>> Option<$a>",
+        "struct Result<T, E> {} fn f1() -> Result<(), Vec<Error>> {foo()}",
+        "struct Result<T, E> {} fn f1() -> Option<Vec<Error>> {foo()}",
     );
 }
 
 #[test]
-fn replace_type() {
+fn replace_struct_init() {
     assert_ssr_transform(
-        "Result<(), $a> ==>> Option<$a>",
-        "struct Result<T, E> {} struct Option<T> {} fn f1() -> Result<(), Vec<Error>> {foo()}",
-        expect![[
-            "struct Result<T, E> {} struct Option<T> {} fn f1() -> Option<Vec<Error>> {foo()}"
-        ]],
+        "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)",
+        "struct Foo {} fn f1() {Foo{b: 1, a: 2}}",
+        "struct Foo {} fn f1() {Foo::new(2, 1)}",
     );
 }
 
@@ -632,12 +462,12 @@ fn replace_macro_invocations() {
     assert_ssr_transform(
         "try!($a) ==>> $a?",
         "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}",
-        expect![["macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}"]],
+        "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}",
     );
     assert_ssr_transform(
         "foo!($a($b)) ==>> foo($b, $a)",
         "macro_rules! foo {() => {}} fn f1() {foo!(abc(def() + 2));}",
-        expect![["macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}"]],
+        "macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}",
     );
 }
 
@@ -646,12 +476,12 @@ fn replace_binary_op() {
     assert_ssr_transform(
         "$a + $b ==>> $b + $a",
         "fn f() {2 * 3 + 4 * 5}",
-        expect![["fn f() {4 * 5 + 2 * 3}"]],
+        "fn f() {4 * 5 + 2 * 3}",
     );
     assert_ssr_transform(
         "$a + $b ==>> $b + $a",
         "fn f() {1 + 2 + 3 + 4}",
-        expect![["fn f() {4 + 3 + 2 + 1}"]],
+        "fn f() {4 + 3 + 2 + 1}",
     );
 }
 
@@ -664,23 +494,8 @@ fn match_binary_op() {
 fn multiple_rules() {
     assert_ssr_transforms(
         &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"],
-        "fn add() {} fn add_one() {} fn f() -> i32 {3 + 2 + 1}",
-        expect![["fn add() {} fn add_one() {} fn f() -> i32 {add_one(add(3, 2))}"]],
-    )
-}
-
-#[test]
-fn multiple_rules_with_nested_matches() {
-    assert_ssr_transforms(
-        &["foo1($a) ==>> bar1($a)", "foo2($a) ==>> bar2($a)"],
-        r#"
-            fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {}
-            fn f() {foo1(foo2(foo1(foo2(foo1(42)))))}
-            "#,
-        expect![[r#"
-            fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {}
-            fn f() {bar1(bar2(bar1(bar2(bar1(42)))))}
-        "#]],
+        "fn f() -> i32 {3 + 2 + 1}",
+        "fn f() -> i32 {add_one(add(3, 2))}",
     )
 }
 
@@ -712,37 +527,12 @@ fn replace_within_macro_expansion() {
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn bar() {}
-            fn f() {macro1!(5.x().foo().o2())}
-            "#,
-        expect![[r#"
-            macro_rules! macro1 {
-                ($a:expr) => {$a}
-            }
-            fn bar() {}
-            fn f() {macro1!(bar(5.x()).o2())}
-            "#]],
-    )
-}
-
-#[test]
-fn replace_outside_and_within_macro_expansion() {
-    assert_ssr_transform(
-        "foo($a) ==>> bar($a)",
+            fn f() {macro1!(5.x().foo().o2())}"#,
         r#"
-            fn foo() {} fn bar() {}
-            macro_rules! macro1 {
-                ($a:expr) => {$a}
-            }
-            fn f() {foo(foo(macro1!(foo(foo(42)))))}
-            "#,
-        expect![[r#"
-            fn foo() {} fn bar() {}
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {bar(bar(macro1!(bar(bar(42)))))}
-        "#]],
+            fn f() {macro1!(bar(5.x()).o2())}"#,
     )
 }
 
@@ -754,14 +544,12 @@ fn preserves_whitespace_within_macro_expansion() {
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {macro1!(1   *   2 + 3 + 4}
-            "#,
-        expect![[r#"
+            fn f() {macro1!(1   *   2 + 3 + 4}"#,
+        r#"
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {macro1!(4 - 3 - 1   *   2}
-            "#]],
+            fn f() {macro1!(4 - 3 - 1   *   2}"#,
     )
 }
 
@@ -792,96 +580,3 @@ fn match_failure_reasons() {
         r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#,
     );
 }
-
-#[test]
-fn overlapping_possible_matches() {
-    // There are three possible matches here, however the middle one, `foo(foo(foo(42)))` shouldn't
-    // match because it overlaps with the outer match. The inner match is permitted since it's is
-    // contained entirely within the placeholder of the outer match.
-    assert_matches(
-        "foo(foo($a))",
-        "fn foo() {} fn main() {foo(foo(foo(foo(42))))}",
-        &["foo(foo(42))", "foo(foo(foo(foo(42))))"],
-    );
-}
-
-#[test]
-fn use_declaration_with_braces() {
-    // It would be OK for a path rule to match and alter a use declaration. We shouldn't mess it up
-    // though. In particular, we must not change `use foo::{baz, bar}` to `use foo::{baz,
-    // foo2::bar2}`.
-    mark::check!(use_declaration_with_braces);
-    assert_ssr_transform(
-        "foo::bar ==>> foo2::bar2",
-        r#"
-        mod foo { pub fn bar() {} pub fn baz() {} }
-        mod foo2 { pub fn bar2() {} }
-        use foo::{baz, bar};
-        fn main() { bar() }
-        "#,
-        expect![["
-        mod foo { pub fn bar() {} pub fn baz() {} }
-        mod foo2 { pub fn bar2() {} }
-        use foo::{baz, bar};
-        fn main() { foo2::bar2() }
-        "]],
-    )
-}
-
-#[test]
-fn ufcs_matches_method_call() {
-    let code = r#"
-    struct Foo {}
-    impl Foo {
-        fn new(_: i32) -> Foo { Foo {} }
-        fn do_stuff(&self, _: i32) {}
-    }
-    struct Bar {}
-    impl Bar {
-        fn new(_: i32) -> Bar { Bar {} }
-        fn do_stuff(&self, v: i32) {}
-    }
-    fn main() {
-        let b = Bar {};
-        let f = Foo {};
-        b.do_stuff(1);
-        f.do_stuff(2);
-        Foo::new(4).do_stuff(3);
-        // Too many / too few args - should never match
-        f.do_stuff(2, 10);
-        f.do_stuff();
-    }
-    "#;
-    assert_matches("Foo::do_stuff($a, $b)", code, &["f.do_stuff(2)", "Foo::new(4).do_stuff(3)"]);
-    // The arguments needs special handling in the case of a function call matching a method call
-    // and the first argument is different.
-    assert_matches("Foo::do_stuff($a, 2)", code, &["f.do_stuff(2)"]);
-    assert_matches("Foo::do_stuff(Foo::new(4), $b)", code, &["Foo::new(4).do_stuff(3)"]);
-
-    assert_ssr_transform(
-        "Foo::do_stuff(Foo::new($a), $b) ==>> Bar::new($b).do_stuff($a)",
-        code,
-        expect![[r#"
-            struct Foo {}
-            impl Foo {
-                fn new(_: i32) -> Foo { Foo {} }
-                fn do_stuff(&self, _: i32) {}
-            }
-            struct Bar {}
-            impl Bar {
-                fn new(_: i32) -> Bar { Bar {} }
-                fn do_stuff(&self, v: i32) {}
-            }
-            fn main() {
-                let b = Bar {};
-                let f = Foo {};
-                b.do_stuff(1);
-                f.do_stuff(2);
-                Bar::new(3).do_stuff(4);
-                // Too many / too few args - should never match
-                f.do_stuff(2, 10);
-                f.do_stuff();
-            }
-        "#]],
-    );
-}