Repository: rust-analyzer/rust-analyzer
Pull request #5518 (2020-07-24 12:05:30)

Use resolved paths in SSR rules
The main user-visible changes are:
* SSR now matches paths based on whether they resolve to the same thing instead of whether they're written the same.
  * So `foo()` won't match `foo()` if it's a different function `foo()`, but will match `bar::foo()` if it's the same `foo`.
* Paths in the replacement will now be rendered with appropriate qualification for their context.
  * For example `foo::Bar` will render as just `Bar` inside the module `foo`, but might render as `baz::foo::Bar` from elsewhere.
* This means that all paths in the search pattern and replacement template must be able to be resolved.
* It now also matters where you invoke SSR from, since paths are resolved relative to wherever that is.
* Search now uses find-uses on paths to locate places to try matching. This means that when a path is present in the pattern, search will generally be pretty fast.
* Function calls can now match method calls again, but this time only if they resolve to the same function.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0a35eb7932..8bed2b1afc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1164,6 +1164,7 @@ dependencies = [
 name = "ra_ssr"
 version = "0.1.0"
 dependencies = [
+ "expect",
  "ra_db",
  "ra_hir",
  "ra_ide_db",
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 02d83e0d95..1dbc095ff3 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -321,7 +321,6 @@ impl<'db> SemanticsImpl<'db> {
         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()) {
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index dc9192d42c..7356e947b9 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -505,9 +505,10 @@ 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)?;
+            let edits = ssr::parse_search_replace(query, parse_only, db, position)?;
             Ok(SourceChange::from(edits))
         })
     }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index b3e9e5dfe1..95d8f79b87 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,5 +1,5 @@
-use ra_db::SourceDatabaseExt;
-use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
+use ra_db::FilePosition;
+use ra_ide_db::RootDatabase;
 
 use crate::SourceFileEdit;
 use ra_ssr::{MatchFinder, SsrError, SsrRule};
@@ -11,6 +11,19 @@ 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:
@@ -43,21 +56,13 @@ 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(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 });
-            }
-        }
+        return Ok(Vec::new());
     }
-    Ok(edits)
+    Ok(match_finder.edits())
 }
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index e06b189a00..f391a8e432 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -290,20 +290,25 @@ pub fn classify_name_ref(
 
     let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
     let resolved = sema.resolve_path(&path)?;
-    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)
+    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),
         }
-        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 81553150b5..a7cae37b0c 100644
--- a/crates/ra_ide_db/src/search.rs
+++ b/crates/ra_ide_db/src/search.rs
@@ -60,6 +60,10 @@ 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_ssr/Cargo.toml b/crates/ra_ssr/Cargo.toml
index fe098aaee3..84e4b171e1 100644
--- a/crates/ra_ssr/Cargo.toml
+++ b/crates/ra_ssr/Cargo.toml
@@ -18,3 +18,6 @@ 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 cca4576ce0..2fb326b45f 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -4,44 +4,41 @@
 //! 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::{record_match_fails_reasons_scope, MatchFailureReason};
+use crate::matching::MatchFailureReason;
 use hir::Semantics;
-use ra_db::{FileId, FileRange};
-use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange};
-use ra_text_edit::TextEdit;
+use ra_db::{FileId, FilePosition, FileRange};
+use ra_ide_db::source_change::SourceFileEdit;
+use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
+use resolving::ResolvedRule;
 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: SsrPattern,
+    pattern: parsing::RawPattern,
     /// What we'll replace it with.
-    template: parsing::SsrTemplate,
+    template: parsing::RawPattern,
+    parsed_rules: Vec<parsing::ParsedRule>,
 }
 
 #[derive(Debug)]
 pub struct SsrPattern {
-    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>,
+    raw: parsing::RawPattern,
+    parsed_rules: Vec<parsing::ParsedRule>,
 }
 
 #[derive(Debug, Default)]
@@ -53,40 +50,112 @@ 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<SsrRule>,
+    rules: Vec<ResolvedRule>,
+    scope: hir::SemanticsScope<'db>,
+    hygiene: hir::Hygiene,
 }
 
 impl<'db> MatchFinder<'db> {
-    pub fn new(db: &'db ra_ide_db::RootDatabase) -> MatchFinder<'db> {
-        MatchFinder { sema: Semantics::new(db), rules: Vec::new() }
+    /// 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 add_rule(&mut self, rule: SsrRule) {
-        self.rules.push(rule);
+    /// 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 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) {
-        self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
+    /// 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(())
     }
 
-    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)))
+    /// 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 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
+    /// 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(())
+    }
+
+    /// 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);
+        }
+        nester::nest_and_remove_collisions(matches, &self.sema)
     }
 
     /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
@@ -115,53 +184,6 @@ 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,
@@ -177,8 +199,17 @@ impl<'db> MatchFinder<'db> {
             }
             if node_range.range == range.range {
                 for rule in &self.rules {
-                    let pattern =
-                        rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
+                    // 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;
+                    }
                     out.push(MatchDebugInfo {
                         matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
                             .map_err(|e| MatchFailureReason {
@@ -186,7 +217,7 @@ impl<'db> MatchFinder<'db> {
                                     "Match failed, but no reason was given".to_owned()
                                 }),
                             }),
-                        pattern,
+                        pattern: rule.pattern.node.clone(),
                         node: node.clone(),
                     });
                 }
@@ -209,9 +240,8 @@ impl<'db> MatchFinder<'db> {
 
 pub struct MatchDebugInfo {
     node: 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>,
+    /// Our search pattern parsed as an expression or item, etc
+    pattern: SyntaxNode,
     matched: Result<Match, MatchFailureReason>,
 }
 
@@ -228,29 +258,12 @@ impl std::fmt::Debug for MatchDebugInfo {
             self.node
         )?;
         writeln!(f, "========= PATTERN ==========")?;
-        match &self.pattern {
-            Ok(pattern) => {
-                writeln!(f, "{:#?}", pattern)?;
-            }
-            Err(err) => {
-                writeln!(f, "{}", err.reason)?;
-            }
-        }
+        writeln!(f, "{:#?}", self.pattern)?;
         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 50b29eab2d..4862622bde 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -2,8 +2,9 @@
 //! process of matching, placeholder values are recorded.
 
 use crate::{
-    parsing::{Constraint, NodeKind, Placeholder, SsrTemplate},
-    SsrMatches, SsrPattern, SsrRule,
+    parsing::{Constraint, NodeKind, Placeholder},
+    resolving::{ResolvedPattern, ResolvedRule},
+    SsrMatches,
 };
 use hir::Semantics;
 use ra_db::FileRange;
@@ -48,9 +49,11 @@ pub struct Match {
     pub(crate) matched_node: SyntaxNode,
     pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
     pub(crate) ignored_comments: Vec<ast::Comment>,
-    // 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,
+    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>,
 }
 
 /// Represents a `$var` in an SSR query.
@@ -86,7 +89,7 @@ pub(crate) struct MatchFailed {
 /// parent module, we don't populate nested matches.
 pub(crate) fn get_match(
     debug_active: bool,
-    rule: &SsrRule,
+    rule: &ResolvedRule,
     code: &SyntaxNode,
     restrict_range: &Option<FileRange>,
     sema: &Semantics<ra_ide_db::RootDatabase>,
@@ -102,7 +105,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 SsrRule,
+    rule: &'sema ResolvedRule,
 }
 
 /// Which phase of matching we're currently performing. We do two phases because most attempted
@@ -117,26 +120,35 @@ enum Phase<'a> {
 
 impl<'db, 'sema> Matcher<'db, 'sema> {
     fn try_match(
-        rule: &'sema SsrRule,
+        rule: &ResolvedRule,
         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, &pattern_tree, code)?;
+        match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, 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(),
-            template: rule.template.clone(),
+            rule_index: rule.index,
+            depth: 0,
+            rendered_template_paths: FxHashMap::default(),
         };
         // 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), &pattern_tree, code)?;
+        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)?;
+        }
         Ok(the_match)
     }
 
@@ -177,10 +189,17 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
             }
             return Ok(());
         }
-        // Non-placeholders.
+        // 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);
+            }
+        }
         if pattern.kind() != code.kind() {
             fail_match!(
-                "Pattern had a `{}` ({:?}), code had `{}` ({:?})",
+                "Pattern had `{}` ({:?}), code had `{}` ({:?})",
                 pattern.text(),
                 pattern.kind(),
                 code.text(),
@@ -194,6 +213,7 @@ 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),
         }
     }
@@ -310,6 +330,64 @@ 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(
@@ -443,9 +521,61 @@ 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.pattern.placeholders_by_stand_in.get(ident.text()))
+        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(())
     }
 }
 
@@ -510,28 +640,6 @@ 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 {
@@ -596,13 +704,12 @@ mod tests {
     #[test]
     fn parse_match_replace() {
         let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
-        let input = "fn foo() {} fn main() { foo(1+2); }";
+        let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
 
-        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);
+        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();
         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);
@@ -615,9 +722,11 @@ mod tests {
             "1+2"
         );
 
-        let edit = crate::replacing::matches_to_edit(&matches, input);
+        let edits = match_finder.edits();
+        assert_eq!(edits.len(), 1);
+        let edit = &edits[0];
         let mut after = input.to_string();
-        edit.apply(&mut after);
-        assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }");
+        edit.edit.apply(&mut after);
+        assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
     }
 }
diff --git a/crates/ra_ssr/src/nester.rs b/crates/ra_ssr/src/nester.rs
new file mode 100644
index 0000000000..b3e20579bd
--- /dev/null
+++ b/crates/ra_ssr/src/nester.rs
@@ -0,0 +1,98 @@
+//! 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 4aee97bb2b..2d6f4e514f 100644
--- a/crates/ra_ssr/src/parsing.rs
+++ b/crates/ra_ssr/src/parsing.rs
@@ -7,17 +7,19 @@
 
 use crate::errors::bail;
 use crate::{SsrError, SsrPattern, SsrRule};
-use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T};
+use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
 use rustc_hash::{FxHashMap, FxHashSet};
 use std::str::FromStr;
 
-#[derive(Clone, Debug)]
-pub(crate) struct SsrTemplate {
-    pub(crate) tokens: Vec<PatternElement>,
+#[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(Debug)]
-pub(crate) struct RawSearchPattern {
+pub(crate) struct RawPattern {
     tokens: Vec<PatternElement>,
 }
 
@@ -54,6 +56,60 @@ 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;
 
@@ -68,21 +124,24 @@ impl FromStr for SsrRule {
         if it.next().is_some() {
             return Err(SsrError("More than one delimiter found".into()));
         }
-        let rule = SsrRule { pattern: pattern.parse()?, template: template.parse()? };
+        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 };
         validate_rule(&rule)?;
         Ok(rule)
     }
 }
 
-impl FromStr for RawSearchPattern {
+impl FromStr for RawPattern {
     type Err = SsrError;
 
-    fn from_str(pattern_str: &str) -> Result<RawSearchPattern, SsrError> {
-        Ok(RawSearchPattern { tokens: parse_pattern(pattern_str)? })
+    fn from_str(pattern_str: &str) -> Result<RawPattern, SsrError> {
+        Ok(RawPattern { tokens: parse_pattern(pattern_str)? })
     }
 }
 
-impl RawSearchPattern {
+impl RawPattern {
     /// 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();
@@ -95,7 +154,7 @@ impl RawSearchPattern {
         res
     }
 
-    fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> {
+    pub(crate) 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 {
@@ -110,41 +169,9 @@ impl FromStr for SsrPattern {
     type Err = SsrError;
 
     fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
-        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 })
+        let raw_pattern = pattern_str.parse()?;
+        let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
+        Ok(SsrPattern { raw: raw_pattern, parsed_rules })
     }
 }
 
@@ -173,7 +200,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.raw.tokens {
+    for p in &rule.pattern.tokens {
         if let PatternElement::Placeholder(placeholder) = p {
             defined_placeholders.insert(&placeholder.ident);
         }
@@ -316,7 +343,7 @@ mod tests {
         }
         let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
         assert_eq!(
-            result.pattern.raw.tokens,
+            result.pattern.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 e43cc51674..4b3f5509c3 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -1,66 +1,124 @@
 //! Code for applying replacement templates for matches that have previously been found.
 
 use crate::matching::Var;
-use crate::parsing::PatternElement;
-use crate::{Match, SsrMatches};
-use ra_syntax::ast::AstToken;
-use ra_syntax::TextSize;
+use crate::{resolving::ResolvedRule, Match, SsrMatches};
+use ra_syntax::ast::{self, AstToken};
+use ra_syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, 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) -> TextEdit {
-    matches_to_edit_at_offset(matches, file_src, 0.into())
+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)
 }
 
 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),
+            render_replace(m, file_src, rules),
         );
     }
     edit_builder.finish()
 }
 
-fn render_replace(match_info: &Match, file_src: &str) -> String {
+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 {
     let mut out = String::new();
-    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
-                    );
+    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);
+                        }
+                    }
                 }
             }
+        } else {
+            self.render_node_children(&node, out);
         }
     }
-    for comment in &match_info.ignored_comments {
-        out.push_str(&comment.syntax().to_string());
+
+    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());
+        }
     }
-    out
 }
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs
new file mode 100644
index 0000000000..75f5567856
--- /dev/null
+++ b/crates/ra_ssr/src/resolving.rs
@@ -0,0 +1,173 @@
+//! 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
new file mode 100644
index 0000000000..bcf0f04689
--- /dev/null
+++ b/crates/ra_ssr/src/search.rs
@@ -0,0 +1,232 @@
+//! 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/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 4fb829ea5c..194bec008d 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -1,27 +1,17 @@
 //! 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::new(db);
+    let mut match_finder = MatchFinder::at_first_file(db)?;
     for rule in rules {
-        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 });
-            }
-        }
+        match_finder.add_rule(rule)?;
     }
+    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();
@@ -38,34 +28,27 @@ 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::new(db);
+    let mut match_finder = MatchFinder::at_first_file(db)?;
     for pattern in patterns {
-        match_finder.add_search_pattern(pattern);
+        match_finder.add_search_pattern(pattern)?;
     }
-    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 {
+    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 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/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 8d8c9442b6..cad92c444e 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1026,8 +1026,9 @@ 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)??;
+        snap.analysis.structural_search_replace(&params.query, params.parse_only, position)??;
     to_proto::workspace_edit(&snap, source_change)
 }
 
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 13ebb18fbb..113e0e070a 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -216,6 +216,11 @@ 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/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 98d14450b1..1be01fd884 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -274,6 +274,11 @@ 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;
 }
 ```
 
@@ -285,7 +290,7 @@ WorkspaceEdit
 
 ### Example
 
-SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
+SSR with query `foo($a, $b) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
 
 ### Unresolved Question
 
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 1f3a7cf7e8..c21e5597cb 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -185,15 +185,21 @@ export function parentModule(ctx: Ctx): Cmd {
 
 export function ssr(ctx: Ctx): Cmd {
     return async () => {
+        const editor = vscode.window.activeTextEditor;
         const client = ctx.client;
-        if (!client) return;
+        if (!editor || !client) return;
+
+        const position = editor.selection.active;
+        const textDocument = { uri: editor.document.uri.toString() };
 
         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 });
+                    await client.sendRequest(ra.ssr, {
+                        query: x, parseOnly: true, textDocument, position,
+                    });
                 } catch (e) {
                     return e.toString();
                 }
@@ -208,7 +214,9 @@ 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 });
+            const edit = await client.sendRequest(ra.ssr, {
+                query: request, parseOnly: false, textDocument, position
+            });
 
             await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
         });
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 5f32cb40e9..149f9a0d64 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -93,6 +93,8 @@ 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');
 


Test patch:
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index f20ae2cdf9..b38807c0f9 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,5 +1,8 @@
 use crate::{MatchFinder, SsrRule};
-use ra_db::{FileId, SourceDatabaseExt};
+use expect::{expect, Expect};
+use ra_db::{salsa::Durability, FileId, FilePosition, SourceDatabaseExt};
+use rustc_hash::FxHashSet;
+use std::sync::Arc;
 use test_utils::mark;
 
 fn parse_error_text(query: &str) -> String {
@@ -36,7 +39,7 @@ fn parser_repeated_name() {
 fn parser_invalid_pattern() {
     assert_eq!(
         parse_error_text(" ==>> ()"),
-        "Parse error: Pattern is not a valid Rust expression, type, item, path or pattern"
+        "Parse error: Not a valid Rust expression, type, item, path or pattern"
     );
 }
 
@@ -44,7 +47,7 @@ fn parser_invalid_pattern() {
 fn parser_invalid_template() {
     assert_eq!(
         parse_error_text("() ==>> )"),
-        "Parse error: Replacement is not a valid Rust expression, type, item, path or pattern"
+        "Parse error: Not a valid Rust expression, type, item, path or pattern"
     );
 }
 
@@ -56,39 +59,44 @@ fn parser_undefined_placeholder_in_replacement() {
     );
 }
 
-fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) {
+/// `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) {
     use ra_db::fixture::WithFixture;
-    ra_ide_db::RootDatabase::with_single_file(code)
-}
-
-fn assert_ssr_transform(rule: &str, input: &str, result: &str) {
-    assert_ssr_transforms(&[rule], input, result);
+    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)
 }
 
-fn normalize_code(code: &str) -> String {
-    let (db, file_id) = single_file(code);
-    db.file_text(file_id).to_string()
+fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) {
+    assert_ssr_transforms(&[rule], input, expected);
 }
 
-fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
-    let (db, file_id) = single_file(input);
-    let mut match_finder = MatchFinder::new(&db);
+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);
     for rule in rules {
         let rule: SsrRule = rule.parse().unwrap();
-        match_finder.add_rule(rule);
+        match_finder.add_rule(rule).unwrap();
     }
-    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 {
+    let edits = match_finder.edits();
+    if edits.is_empty() {
         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) {
@@ -104,39 +112,34 @@ fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet:
 }
 
 fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
-    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();
+    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();
     if matched_strings != expected && !expected.is_empty() {
-        print_match_debug_info(&match_finder, file_id, &expected[0]);
+        print_match_debug_info(&match_finder, position.file_id, &expected[0]);
     }
     assert_eq!(matched_strings, expected);
 }
 
 fn assert_no_match(pattern: &str, code: &str) {
-    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;
+    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;
     if !matches.is_empty() {
-        print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
+        print_match_debug_info(&match_finder, position.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, file_id) = single_file(code);
-    let mut match_finder = MatchFinder::new(&db);
-    match_finder.add_search_pattern(pattern.parse().unwrap());
+    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 mut reasons = Vec::new();
-    for d in match_finder.debug_where_text_equal(file_id, snippet) {
+    for d in match_finder.debug_where_text_equal(position.file_id, snippet) {
         if let Some(reason) = d.match_failure_reason() {
             reasons.push(reason.to_owned());
         }
@@ -149,7 +152,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) } }",
-        "fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }",
+        expect![["fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }"]],
     )
 }
 
@@ -157,8 +160,19 @@ fn ssr_function_to_method() {
 fn ssr_nested_function() {
     assert_ssr_transform(
         "foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
-        "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)) }",
+        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)) }
+        "#]],
     )
 }
 
@@ -167,7 +181,7 @@ fn ssr_expected_spacing() {
     assert_ssr_transform(
         "foo($x) + bar() ==>> bar($x)",
         "fn foo() {} fn bar() {} fn main() { foo(5) + bar() }",
-        "fn foo() {} fn bar() {} fn main() { bar(5) }",
+        expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
     );
 }
 
@@ -176,7 +190,7 @@ fn ssr_with_extra_space() {
     assert_ssr_transform(
         "foo($x  ) +    bar() ==>> bar($x)",
         "fn foo() {} fn bar() {} fn main() { foo(  5 )  +bar(   ) }",
-        "fn foo() {} fn bar() {} fn main() { bar(5) }",
+        expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
     );
 }
 
@@ -184,8 +198,8 @@ fn ssr_with_extra_space() {
 fn ssr_keeps_nested_comment() {
     assert_ssr_transform(
         "foo($x) ==>> bar($x)",
-        "fn foo() {} fn main() { foo(other(5 /* using 5 */)) }",
-        "fn foo() {} fn main() { bar(other(5 /* using 5 */)) }",
+        "fn foo() {} fn bar() {} fn main() { foo(other(5 /* using 5 */)) }",
+        expect![["fn foo() {} fn bar() {} fn main() { bar(other(5 /* using 5 */)) }"]],
     )
 }
 
@@ -193,17 +207,25 @@ fn ssr_keeps_nested_comment() {
 fn ssr_keeps_comment() {
     assert_ssr_transform(
         "foo($x) ==>> bar($x)",
-        "fn foo() {} fn main() { foo(5 /* using 5 */) }",
-        "fn foo() {} fn main() { bar(5)/* using 5 */ }",
+        "fn foo() {} fn bar() {} fn main() { foo(5 /* using 5 */) }",
+        expect![["fn foo() {} fn bar() {} fn main() { bar(5)/* using 5 */ }"]],
     )
 }
 
 #[test]
 fn ssr_struct_lit() {
     assert_ssr_transform(
-        "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) }",
+        "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) }
+        "#]],
     )
 }
 
@@ -315,7 +337,7 @@ fn match_struct_instantiation() {
 fn match_path() {
     let code = r#"
         mod foo {
-            fn bar() {}
+            pub fn bar() {}
         }
         fn f() {foo::bar(42)}"#;
     assert_matches("foo::bar", code, &["foo::bar"]);
@@ -328,6 +350,60 @@ 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);
@@ -416,8 +492,8 @@ fn no_match_split_expression() {
 fn replace_function_call() {
     assert_ssr_transform(
         "foo() ==>> bar()",
-        "fn foo() {} fn f1() {foo(); foo();}",
-        "fn foo() {} fn f1() {bar(); bar();}",
+        "fn foo() {} fn bar() {} fn f1() {foo(); foo();}",
+        expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]],
     );
 }
 
@@ -425,8 +501,8 @@ fn replace_function_call() {
 fn replace_function_call_with_placeholders() {
     assert_ssr_transform(
         "foo($a, $b) ==>> bar($b, $a)",
-        "fn foo() {} fn f1() {foo(5, 42)}",
-        "fn foo() {} fn f1() {bar(42, 5)}",
+        "fn foo() {} fn bar() {} fn f1() {foo(5, 42)}",
+        expect![["fn foo() {} fn bar() {} fn f1() {bar(42, 5)}"]],
     );
 }
 
@@ -434,26 +510,120 @@ fn replace_function_call_with_placeholders() {
 fn replace_nested_function_calls() {
     assert_ssr_transform(
         "foo($a) ==>> bar($a)",
-        "fn foo() {} fn f1() {foo(foo(42))}",
-        "fn foo() {} fn f1() {bar(bar(42))}",
+        "fn foo() {} fn bar() {} fn f1() {foo(foo(42))}",
+        expect![["fn foo() {} fn bar() {} fn f1() {bar(bar(42))}"]],
     );
 }
 
 #[test]
-fn replace_type() {
+fn replace_associated_function_call() {
     assert_ssr_transform(
-        "Result<(), $a> ==>> Option<$a>",
-        "struct Result<T, E> {} fn f1() -> Result<(), Vec<Error>> {foo()}",
-        "struct Result<T, E> {} fn f1() -> Option<Vec<Error>> {foo()}",
+        "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() }
+            "#]],
     );
 }
 
 #[test]
-fn replace_struct_init() {
+fn replace_associated_function_with_generics() {
     assert_ssr_transform(
-        "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)}",
+        "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();
+                }
+            }
+            "#]],
+    );
+}
+
+#[test]
+fn replace_type() {
+    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()}"
+        ]],
     );
 }
 
@@ -462,12 +632,12 @@ fn replace_macro_invocations() {
     assert_ssr_transform(
         "try!($a) ==>> $a?",
         "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}",
-        "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}",
+        expect![["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));}",
-        "macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}",
+        expect![["macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}"]],
     );
 }
 
@@ -476,12 +646,12 @@ fn replace_binary_op() {
     assert_ssr_transform(
         "$a + $b ==>> $b + $a",
         "fn f() {2 * 3 + 4 * 5}",
-        "fn f() {4 * 5 + 2 * 3}",
+        expect![["fn f() {4 * 5 + 2 * 3}"]],
     );
     assert_ssr_transform(
         "$a + $b ==>> $b + $a",
         "fn f() {1 + 2 + 3 + 4}",
-        "fn f() {4 + 3 + 2 + 1}",
+        expect![["fn f() {4 + 3 + 2 + 1}"]],
     );
 }
 
@@ -494,8 +664,23 @@ fn match_binary_op() {
 fn multiple_rules() {
     assert_ssr_transforms(
         &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"],
-        "fn f() -> i32 {3 + 2 + 1}",
-        "fn f() -> i32 {add_one(add(3, 2))}",
+        "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)))))}
+        "#]],
     )
 }
 
@@ -527,12 +712,37 @@ fn replace_within_macro_expansion() {
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {macro1!(5.x().foo().o2())}"#,
+            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)",
         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() {macro1!(bar(5.x()).o2())}"#,
+            fn f() {bar(bar(macro1!(bar(bar(42)))))}
+        "#]],
     )
 }
 
@@ -544,12 +754,14 @@ fn preserves_whitespace_within_macro_expansion() {
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {macro1!(1   *   2 + 3 + 4}"#,
-        r#"
+            fn f() {macro1!(1   *   2 + 3 + 4}
+            "#,
+        expect![[r#"
             macro_rules! macro1 {
                 ($a:expr) => {$a}
             }
-            fn f() {macro1!(4 - 3 - 1   *   2}"#,
+            fn f() {macro1!(4 - 3 - 1   *   2}
+            "#]],
     )
 }
 
@@ -580,3 +792,96 @@ 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();
+            }
+        "#]],
+    );
+}