Repository: rust-analyzer/rust-analyzer
Pull request #10458 (2021-10-04 15:51:26)

feat: Implement custom user snippets
![Y24dX7fOWX](https://user-images.githubusercontent.com/3757771/136059454-ceccfc2c-2c90-46da-8ad1-bac9c2e83ec1.gif)

Allows us to address the following issues:
    - `.arc / .rc / .pin, similar to .box?` https://github.com/rust-analyzer/rust-analyzer/issues/7033
    - `Add unsafe snippet` https://github.com/rust-analyzer/rust-analyzer/issues/10392, would allow users to have this without the diagnostic)
    - `.ok() postfix snippet is annoying` https://github.com/rust-analyzer/rust-analyzer/issues/9636, allows us to get rid of the `ok` postfix and similar ones
    - `Postfix vec completion` https://github.com/rust-analyzer/rust-analyzer/issues/7773

cc https://github.com/rust-analyzer/rust-analyzer/issues/772

Zulipd discussion: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/Custom.20Postfix.20snippets


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index d1058c02c7..9ad34f7160 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -633,6 +633,7 @@ dependencies = [
  "once_cell",
  "profile",
  "rustc-hash",
+ "smallvec",
  "sourcegen",
  "stdx",
  "syntax",
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 0661d776f1..40dfe6f511 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -179,7 +179,7 @@ impl FlycheckActor {
                         tracing::error!(
                             "Flycheck failed to run the following command: {:?}",
                             self.check_command()
-                        );
+                        )
                     }
                     self.progress(Progress::DidFinish(res));
                 }
@@ -253,14 +253,14 @@ impl FlycheckActor {
     }
 
     fn send(&self, check_task: Message) {
-        (self.sender)(check_task);
+        (self.sender)(check_task)
     }
 }
 
 struct CargoHandle {
     child: JodChild,
     #[allow(unused)]
-    thread: jod_thread::JoinHandle<bool>,
+    thread: jod_thread::JoinHandle<io::Result<bool>>,
     receiver: Receiver<CargoMessage>,
 }
 
@@ -279,7 +279,7 @@ impl CargoHandle {
         // It is okay to ignore the result, as it only errors if the process is already dead
         let _ = self.child.kill();
         let exit_status = self.child.wait()?;
-        let read_at_least_one_message = self.thread.join();
+        let read_at_least_one_message = self.thread.join()?;
         if !exit_status.success() && !read_at_least_one_message {
             // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
             // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
@@ -304,7 +304,7 @@ impl CargoActor {
     fn new(child_stdout: process::ChildStdout, sender: Sender<CargoMessage>) -> CargoActor {
         CargoActor { child_stdout, sender }
     }
-    fn run(self) -> bool {
+    fn run(self) -> io::Result<bool> {
         // We manually read a line at a time, instead of using serde's
         // stream deserializers, because the deserializer cannot recover
         // from an error, resulting in it getting stuck, because we try to
@@ -334,20 +334,20 @@ impl CargoActor {
                     // Skip certain kinds of messages to only spend time on what's useful
                     JsonMessage::Cargo(message) => match message {
                         cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => {
-                            self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
+                            self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap()
                         }
                         cargo_metadata::Message::CompilerMessage(msg) => {
-                            self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
+                            self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap()
                         }
                         _ => (),
                     },
                     JsonMessage::Rustc(message) => {
-                        self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
+                        self.sender.send(CargoMessage::Diagnostic(message)).unwrap()
                     }
                 }
             }
         }
-        read_at_least_one_message
+        Ok(read_at_least_one_message)
     }
 }
 
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index a541cbd5e1..aa4f25a3b4 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -241,7 +241,7 @@ impl HirDisplay for TypeParam {
             return Ok(());
         }
 
-        let bounds = f.db.generic_predicates_for_param(self.id, None);
+        let bounds = f.db.generic_predicates_for_param(self.id);
         let substs = TyBuilder::type_params_subst(f.db, self.id.parent);
         let predicates: Vec<_> =
             bounds.iter().cloned().map(|b| b.substitute(&Interner, &substs)).collect();
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index c356a71ed6..ff795d85be 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1119,7 +1119,7 @@ impl DefWithBody {
                                 if let ast::Expr::RecordExpr(record_expr) =
                                     &source_ptr.value.to_node(&root)
                                 {
-                                    if record_expr.record_expr_field_list().is_some() {
+                                    if let Some(_) = record_expr.record_expr_field_list() {
                                         acc.push(
                                             MissingFields {
                                                 file: source_ptr.file_id,
@@ -1143,7 +1143,7 @@ impl DefWithBody {
                                 if let Some(expr) = source_ptr.value.as_ref().left() {
                                     let root = source_ptr.file_syntax(db.upcast());
                                     if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
-                                        if record_pat.record_pat_field_list().is_some() {
+                                        if let Some(_) = record_pat.record_pat_field_list() {
                                             acc.push(
                                                 MissingFields {
                                                     file: source_ptr.file_id,
@@ -2024,7 +2024,7 @@ impl TypeParam {
     }
 
     pub fn trait_bounds(self, db: &dyn HirDatabase) -> Vec<Trait> {
-        db.generic_predicates_for_param(self.id, None)
+        db.generic_predicates_for_param(self.id)
             .iter()
             .filter_map(|pred| match &pred.skip_binders().skip_binders() {
                 hir_ty::WhereClause::Implemented(trait_ref) => {
@@ -2119,9 +2119,10 @@ impl Impl {
         };
 
         let fp = TyFingerprint::for_inherent_impl(&ty);
-        let fp = match fp {
-            Some(fp) => fp,
-            None => return Vec::new(),
+        let fp = if let Some(fp) = fp {
+            fp
+        } else {
+            return Vec::new();
         };
 
         let mut all = Vec::new();
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 4c1a3344ec..804d98ce38 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -474,9 +474,10 @@ impl ExprCollector<'_> {
             }
             ast::Expr::PrefixExpr(e) => {
                 let expr = self.collect_expr_opt(e.expr());
-                match e.op_kind() {
-                    Some(op) => self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr),
-                    None => self.alloc_expr(Expr::Missing, syntax_ptr),
+                if let Some(op) = e.op_kind() {
+                    self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr)
+                } else {
+                    self.alloc_expr(Expr::Missing, syntax_ptr)
                 }
             }
             ast::Expr::ClosureExpr(e) => {
@@ -623,9 +624,10 @@ impl ExprCollector<'_> {
     }
 
     fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
-        match expr {
-            Some(expr) => self.collect_expr(expr),
-            None => self.missing_expr(),
+        if let Some(expr) = expr {
+            self.collect_expr(expr)
+        } else {
+            self.missing_expr()
         }
     }
 
@@ -722,9 +724,10 @@ impl ExprCollector<'_> {
     }
 
     fn collect_block_opt(&mut self, expr: Option<ast::BlockExpr>) -> ExprId {
-        match expr {
-            Some(block) => self.collect_block(block),
-            None => self.missing_expr(),
+        if let Some(block) = expr {
+            self.collect_block(block)
+        } else {
+            self.missing_expr()
         }
     }
 
@@ -887,9 +890,10 @@ impl ExprCollector<'_> {
     }
 
     fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
-        match pat {
-            Some(pat) => self.collect_pat(pat),
-            None => self.missing_pat(),
+        if let Some(pat) = pat {
+            self.collect_pat(pat)
+        } else {
+            self.missing_pat()
         }
     }
 
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 3251638c7a..08ffa8e53d 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -209,9 +209,10 @@ fn find_path_inner(
             ) {
                 path.push_segment(name);
 
-                let new_path = match best_path {
-                    Some(best_path) => select_best_path(best_path, path, prefer_no_std),
-                    None => path,
+                let new_path = if let Some(best_path) = best_path {
+                    select_best_path(best_path, path, prefer_no_std)
+                } else {
+                    path
                 };
                 best_path_len = new_path.len();
                 best_path = Some(new_path);
@@ -242,9 +243,10 @@ fn find_path_inner(
         });
 
         for path in extern_paths {
-            let new_path = match best_path {
-                Some(best_path) => select_best_path(best_path, path, prefer_no_std),
-                None => path,
+            let new_path = if let Some(best_path) = best_path {
+                select_best_path(best_path, path, prefer_no_std)
+            } else {
+                path
             };
             best_path = Some(new_path);
         }
@@ -259,11 +261,12 @@ fn find_path_inner(
         }
     }
 
-    match prefixed.map(PrefixKind::prefix) {
-        Some(prefix) => best_path.or_else(|| {
+    if let Some(prefix) = prefixed.map(PrefixKind::prefix) {
+        best_path.or_else(|| {
             scope_name.map(|scope_name| ModPath::from_segments(prefix, vec![scope_name]))
-        }),
-        None => best_path,
+        })
+    } else {
+        best_path
     }
 }
 
@@ -343,13 +346,15 @@ fn find_local_import_locations(
 
         if let Some((name, vis)) = data.scope.name_of(item) {
             if vis.is_visible_from(db, from) {
-                let is_private = match vis {
-                    Visibility::Module(private_to) => private_to.local_id == module.local_id,
-                    Visibility::Public => false,
+                let is_private = if let Visibility::Module(private_to) = vis {
+                    private_to.local_id == module.local_id
+                } else {
+                    false
                 };
-                let is_original_def = match item.as_module_def_id() {
-                    Some(module_def_id) => data.scope.declarations().any(|it| it == module_def_id),
-                    None => false,
+                let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
+                    data.scope.declarations().any(|it| it == module_def_id)
+                } else {
+                    false
                 };
 
                 // Ignore private imports. these could be used if we are
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index b7a5758a0a..3900e7e97c 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -475,9 +475,10 @@ macro_rules! mod_items {
                 }
 
                 fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>> {
-                    match mod_item {
-                        ModItem::$typ(id) => Some(id),
-                        _ => None,
+                    if let ModItem::$typ(id) = mod_item {
+                        Some(id)
+                    } else {
+                        None
                     }
                 }
 
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs
index 30f11cc694..4fb81bde46 100644
--- a/crates/hir_def/src/nameres/path_resolution.rs
+++ b/crates/hir_def/src/nameres/path_resolution.rs
@@ -400,10 +400,13 @@ impl DefMap {
         };
         let from_scope_or_builtin = match shadow {
             BuiltinShadowMode::Module => from_scope.or(from_builtin),
-            BuiltinShadowMode::Other => match from_scope.take_types() {
-                Some(ModuleDefId::ModuleId(_)) => from_builtin.or(from_scope),
-                Some(_) | None => from_scope.or(from_builtin),
-            },
+            BuiltinShadowMode::Other => {
+                if let Some(ModuleDefId::ModuleId(_)) = from_scope.take_types() {
+                    from_builtin.or(from_scope)
+                } else {
+                    from_scope.or(from_builtin)
+                }
+            }
         };
         let from_extern_prelude = self
             .extern_prelude
diff --git a/crates/hir_def/src/path/lower/lower_use.rs b/crates/hir_def/src/path/lower/lower_use.rs
index 6b777cfeff..0ee406f63a 100644
--- a/crates/hir_def/src/path/lower/lower_use.rs
+++ b/crates/hir_def/src/path/lower/lower_use.rs
@@ -18,9 +18,10 @@ pub(crate) fn convert_path(
     path: ast::Path,
     hygiene: &Hygiene,
 ) -> Option<ModPath> {
-    let prefix = match path.qualifier() {
-        Some(qual) => Some(convert_path(db, prefix, qual, hygiene)?),
-        None => prefix,
+    let prefix = if let Some(qual) = path.qualifier() {
+        Some(convert_path(db, prefix, qual, hygiene)?)
+    } else {
+        prefix
     };
 
     let segment = path.segment()?;
diff --git a/crates/hir_def/src/type_ref.rs b/crates/hir_def/src/type_ref.rs
index cfc69feccc..9f33fd26e8 100644
--- a/crates/hir_def/src/type_ref.rs
+++ b/crates/hir_def/src/type_ref.rs
@@ -214,9 +214,10 @@ impl TypeRef {
     }
 
     pub(crate) fn from_ast_opt(ctx: &LowerCtx, node: Option<ast::Type>) -> Self {
-        match node {
-            Some(node) => TypeRef::from_ast(ctx, node),
-            None => TypeRef::Error,
+        if let Some(node) = node {
+            TypeRef::from_ast(ctx, node)
+        } else {
+            TypeRef::Error
         }
     }
 
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 552603dce7..9c844c6138 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -48,9 +48,10 @@ impl Name {
 
     /// Resolve a name from the text of token.
     fn resolve(raw_text: &str) -> Name {
-        match raw_text.strip_prefix("r#") {
-            Some(text) => Name::new_text(SmolStr::new(text)),
-            None => Name::new_text(raw_text.into()),
+        if let Some(text) = raw_text.strip_prefix("r#") {
+            Name::new_text(SmolStr::new(text))
+        } else {
+            Name::new_text(raw_text.into())
         }
     }
 
diff --git a/crates/hir_ty/src/autoderef.rs b/crates/hir_ty/src/autoderef.rs
index 4dc46a2cde..e3f008645c 100644
--- a/crates/hir_ty/src/autoderef.rs
+++ b/crates/hir_ty/src/autoderef.rs
@@ -109,9 +109,10 @@ pub(crate) fn deref(
     ty: InEnvironment<&Canonical<Ty>>,
 ) -> Option<Canonical<Ty>> {
     let _p = profile::span("deref");
-    match builtin_deref(&ty.goal.value) {
-        Some(derefed) => Some(Canonical { value: derefed, binders: ty.goal.binders.clone() }),
-        None => deref_by_trait(db, krate, ty),
+    if let Some(derefed) = builtin_deref(&ty.goal.value) {
+        Some(Canonical { value: derefed, binders: ty.goal.binders.clone() })
+    } else {
+        deref_by_trait(db, krate, ty)
     }
 }
 
diff --git a/crates/hir_ty/src/chalk_ext.rs b/crates/hir_ty/src/chalk_ext.rs
index 36c56820eb..c29fbd688a 100644
--- a/crates/hir_ty/src/chalk_ext.rs
+++ b/crates/hir_ty/src/chalk_ext.rs
@@ -104,9 +104,10 @@ impl TyExt for Ty {
     }
 
     fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId> {
-        match self.callable_def(db) {
-            Some(CallableDefId::FunctionId(func)) => Some(func),
-            Some(CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_)) | None => None,
+        if let Some(CallableDefId::FunctionId(func)) = self.callable_def(db) {
+            Some(func)
+        } else {
+            None
         }
     }
     fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)> {
diff --git a/crates/hir_ty/src/db.rs b/crates/hir_ty/src/db.rs
index 98a5849001..b9003c413b 100644
--- a/crates/hir_ty/src/db.rs
+++ b/crates/hir_ty/src/db.rs
@@ -61,7 +61,6 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     fn generic_predicates_for_param(
         &self,
         param_id: TypeParamId,
-        assoc_name: Option<Name>,
     ) -> Arc<[Binders<QuantifiedWhereClause>]>;
 
     #[salsa::invoke(crate::lower::generic_predicates_query)]
diff --git a/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs b/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
index f9439c706e..2fa456a035 100644
--- a/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
+++ b/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
@@ -105,9 +105,10 @@ impl IntRange {
 
     #[inline]
     fn from_range(lo: u128, hi: u128, scalar_ty: Scalar) -> IntRange {
-        match scalar_ty {
-            Scalar::Bool => IntRange { range: lo..=hi },
-            _ => unimplemented!(),
+        if let Scalar::Bool = scalar_ty {
+            IntRange { range: lo..=hi }
+        } else {
+            unimplemented!()
         }
     }
 
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index adce43aa07..1ae718a36b 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -167,9 +167,10 @@ impl<'a> HirFormatter<'a> {
     }
 
     pub fn should_truncate(&self) -> bool {
-        match self.max_size {
-            Some(max_size) => self.curr_size >= max_size,
-            None => false,
+        if let Some(max_size) = self.max_size {
+            self.curr_size >= max_size
+        } else {
+            false
         }
     }
 
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index f5bc898a85..b0306d9148 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -264,9 +264,10 @@ impl<'a> InferenceContext<'a> {
 
                 // collect explicitly written argument types
                 for arg_type in arg_types.iter() {
-                    let arg_ty = match arg_type {
-                        Some(type_ref) => self.make_ty(type_ref),
-                        None => self.table.new_type_var(),
+                    let arg_ty = if let Some(type_ref) = arg_type {
+                        self.make_ty(type_ref)
+                    } else {
+                        self.table.new_type_var()
                     };
                     sig_tys.push(arg_ty);
                 }
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index 9aaf7db87b..957cd829aa 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -204,9 +204,10 @@ impl<'a> InferenceContext<'a> {
                 } else {
                     BindingMode::convert(*mode)
                 };
-                let inner_ty = match subpat {
-                    Some(subpat) => self.infer_pat(*subpat, &expected, default_bm),
-                    None => expected,
+                let inner_ty = if let Some(subpat) = subpat {
+                    self.infer_pat(*subpat, &expected, default_bm)
+                } else {
+                    expected
                 };
                 let inner_ty = self.insert_type_vars_shallow(inner_ty);
 
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index c40ef7f587..4c1e758904 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -324,9 +324,10 @@ impl<'a> InferenceTable<'a> {
 
     /// Unify two types and register new trait goals that arise from that.
     pub(crate) fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
-        let result = match self.try_unify(ty1, ty2) {
-            Ok(r) => r,
-            Err(_) => return false,
+        let result = if let Ok(r) = self.try_unify(ty1, ty2) {
+            r
+        } else {
+            return false;
         };
         self.register_infer_ok(result);
         true
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index af1d5fdb6a..3734eb1013 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -30,7 +30,6 @@ use smallvec::SmallVec;
 use stdx::impl_from;
 use syntax::ast;
 
-use crate::all_super_traits;
 use crate::{
     consteval,
     db::HirDatabase,
@@ -369,9 +368,10 @@ impl<'a> TyLoweringContext<'a> {
                 Some((it, None)) => it,
                 _ => return None,
             };
-        match resolution {
-            TypeNs::GenericParam(param_id) => Some(param_id),
-            _ => None,
+        if let TypeNs::GenericParam(param_id) = resolution {
+            Some(param_id)
+        } else {
+            None
         }
     }
 
@@ -531,10 +531,9 @@ impl<'a> TyLoweringContext<'a> {
 
     fn select_associated_type(&self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
         if let Some(res) = res {
-            let ty = named_associated_type_shorthand_candidates(
+            let ty = associated_type_shorthand_candidates(
                 self.db,
                 res,
-                Some(segment.name.clone()),
                 move |name, t, associated_ty| {
                     if name == segment.name {
                         let substs = match self.type_param_mode {
@@ -556,16 +555,16 @@ impl<'a> TyLoweringContext<'a> {
                         // associated_type_shorthand_candidates does not do that
                         let substs = substs.shifted_in_from(&Interner, self.in_binders);
                         // FIXME handle type parameters on the segment
-                        Some(
+                        return Some(
                             TyKind::Alias(AliasTy::Projection(ProjectionTy {
                                 associated_ty_id: to_assoc_type_id(associated_ty),
                                 substitution: substs,
                             }))
                             .intern(&Interner),
-                        )
-                    } else {
-                        None
+                        );
                     }
+
+                    None
                 },
             );
 
@@ -936,15 +935,6 @@ pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDefId) -> PolyFnSig
 pub fn associated_type_shorthand_candidates<R>(
     db: &dyn HirDatabase,
     res: TypeNs,
-    cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
-) -> Option<R> {
-    named_associated_type_shorthand_candidates(db, res, None, cb)
-}
-
-fn named_associated_type_shorthand_candidates<R>(
-    db: &dyn HirDatabase,
-    res: TypeNs,
-    assoc_name: Option<Name>,
     mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
 ) -> Option<R> {
     let mut search = |t| {
@@ -969,7 +959,7 @@ fn named_associated_type_shorthand_candidates<R>(
             db.impl_trait(impl_id)?.into_value_and_skipped_binders().0,
         ),
         TypeNs::GenericParam(param_id) => {
-            let predicates = db.generic_predicates_for_param(param_id, assoc_name);
+            let predicates = db.generic_predicates_for_param(param_id);
             let res = predicates.iter().find_map(|pred| match pred.skip_binders().skip_binders() {
                 // FIXME: how to correctly handle higher-ranked bounds here?
                 WhereClause::Implemented(tr) => search(
@@ -1032,7 +1022,6 @@ pub(crate) fn field_types_query(
 pub(crate) fn generic_predicates_for_param_query(
     db: &dyn HirDatabase,
     param_id: TypeParamId,
-    assoc_name: Option<Name>,
 ) -> Arc<[Binders<QuantifiedWhereClause>]> {
     let resolver = param_id.parent.resolver(db.upcast());
     let ctx =
@@ -1042,46 +1031,13 @@ pub(crate) fn generic_predicates_for_param_query(
         .where_predicates_in_scope()
         // we have to filter out all other predicates *first*, before attempting to lower them
         .filter(|pred| match pred {
-            WherePredicate::ForLifetime { target, bound, .. }
-            | WherePredicate::TypeBound { target, bound, .. } => {
-                match target {
-                    WherePredicateTypeTarget::TypeRef(type_ref) => {
-                        if ctx.lower_ty_only_param(type_ref) != Some(param_id) {
-                            return false;
-                        }
-                    }
-                    WherePredicateTypeTarget::TypeParam(local_id) => {
-                        if *local_id != param_id.local_id {
-                            return false;
-                        }
-                    }
-                };
-
-                match &**bound {
-                    TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => {
-                        // Only lower the bound if the trait could possibly define the associated
-                        // type we're looking for.
-
-                        let assoc_name = match &assoc_name {
-                            Some(it) => it,
-                            None => return true,
-                        };
-                        let tr = match resolver
-                            .resolve_path_in_type_ns_fully(db.upcast(), path.mod_path())
-                        {
-                            Some(TypeNs::TraitId(tr)) => tr,
-                            _ => return false,
-                        };
-
-                        all_super_traits(db.upcast(), tr).iter().any(|tr| {
-                            db.trait_data(*tr).items.iter().any(|(name, item)| {
-                                matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name
-                            })
-                        })
-                    }
-                    TypeBound::Lifetime(_) | TypeBound::Error => false,
+            WherePredicate::ForLifetime { target, .. }
+            | WherePredicate::TypeBound { target, .. } => match target {
+                WherePredicateTypeTarget::TypeRef(type_ref) => {
+                    ctx.lower_ty_only_param(type_ref) == Some(param_id)
                 }
-            }
+                WherePredicateTypeTarget::TypeParam(local_id) => *local_id == param_id.local_id,
+            },
             WherePredicate::Lifetime { .. } => false,
         })
         .flat_map(|pred| ctx.lower_where_predicate(pred, true).map(|p| make_binders(&generics, p)))
@@ -1100,7 +1056,6 @@ pub(crate) fn generic_predicates_for_param_recover(
     _db: &dyn HirDatabase,
     _cycle: &[String],
     _param_id: &TypeParamId,
-    _assoc_name: &Option<Name>,
 ) -> Arc<[Binders<QuantifiedWhereClause>]> {
     Arc::new([])
 }
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index 8e6ab8af0f..c88a8b6535 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -82,9 +82,10 @@ impl TyFingerprint {
             TyKind::Ref(_, _, ty) => return TyFingerprint::for_trait_impl(ty),
             TyKind::Tuple(_, subst) => {
                 let first_ty = subst.interned().get(0).map(|arg| arg.assert_ty_ref(&Interner));
-                match first_ty {
-                    Some(ty) => return TyFingerprint::for_trait_impl(ty),
-                    None => TyFingerprint::Unit,
+                if let Some(ty) = first_ty {
+                    return TyFingerprint::for_trait_impl(ty);
+                } else {
+                    TyFingerprint::Unit
                 }
             }
             TyKind::AssociatedType(_, _)
diff --git a/crates/hir_ty/src/utils.rs b/crates/hir_ty/src/utils.rs
index 07898f0088..14b8a726a8 100644
--- a/crates/hir_ty/src/utils.rs
+++ b/crates/hir_ty/src/utils.rs
@@ -79,7 +79,7 @@ fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef) -> Vec<Tr
         Some(p) => TypeParamId { parent: trait_ref.hir_trait_id().into(), local_id: p },
         None => return Vec::new(),
     };
-    db.generic_predicates_for_param(trait_self, None)
+    db.generic_predicates_for_param(trait_self)
         .iter()
         .filter_map(|pred| {
             pred.as_ref().filter_map(|pred| match pred.skip_binders() {
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 01c1259fc5..65c29f6805 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -292,9 +292,10 @@ impl TryToNav for hir::Impl {
     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
         let src = self.source(db)?;
         let derive_attr = self.is_builtin_derive(db);
-        let frange = match &derive_attr {
-            Some(item) => item.syntax().original_file_range(db),
-            None => src.syntax().original_file_range(db),
+        let frange = if let Some(item) = &derive_attr {
+            item.syntax().original_file_range(db)
+        } else {
+            src.syntax().original_file_range(db)
         };
         let focus_range = if derive_attr.is_some() {
             None
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 4851f9aee0..000d74abac 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -136,9 +136,10 @@ fn remove_newline(
             }
             T!['}'] => {
                 // Removes: comma, newline (incl. surrounding whitespace)
-                let space = match prev.prev_sibling_or_token() {
-                    Some(left) => compute_ws(left.kind(), next.kind()),
-                    None => " ",
+                let space = if let Some(left) = prev.prev_sibling_or_token() {
+                    compute_ws(left.kind(), next.kind())
+                } else {
+                    " "
                 };
                 edit.replace(
                     TextRange::new(prev.text_range().start(), token.text_range().end()),
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 5c47227990..35b6a758d4 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -98,7 +98,8 @@ pub use ide_assists::{
     Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
 };
 pub use ide_completion::{
-    CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit,
+    CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, Snippet,
+    SnippetScope,
 };
 pub use ide_db::{
     base_db::{
@@ -532,19 +533,10 @@ impl Analysis {
         &self,
         config: &CompletionConfig,
         position: FilePosition,
-        full_import_path: &str,
-        imported_name: String,
+        imports: impl IntoIterator<Item = (String, String)> + std::panic::UnwindSafe,
     ) -> Cancellable<Vec<TextEdit>> {
         Ok(self
-            .with_db(|db| {
-                ide_completion::resolve_completion_edits(
-                    db,
-                    config,
-                    position,
-                    full_import_path,
-                    imported_name,
-                )
-            })?
+            .with_db(|db| ide_completion::resolve_completion_edits(db, config, position, imports))?
             .unwrap_or_default())
     }
 
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index e02c0dfd84..cce342272f 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -156,7 +156,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
         _ => bail!("Cannot rename local to self outside of function"),
     };
 
-    if fn_def.self_param(sema.db).is_some() {
+    if let Some(_) = fn_def.self_param(sema.db) {
         bail!("Method already has a self parameter");
     }
 
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index eb1a04c3f7..aa62e2eae5 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -103,9 +103,10 @@ impl StaticIndex<'_> {
         for token in tokens {
             let range = token.text_range();
             let node = token.parent().unwrap();
-            let def = match get_definition(&sema, token.clone()) {
-                Some(x) => x,
-                None => continue,
+            let def = if let Some(x) = get_definition(&sema, token.clone()) {
+                x
+            } else {
+                continue;
             };
             let id = if let Some(x) = self.def_map.get(&def) {
                 *x
@@ -123,9 +124,10 @@ impl StaticIndex<'_> {
             let token = self.tokens.get_mut(id).unwrap();
             token.references.push(ReferenceData {
                 range: FileRange { range, file_id },
-                is_definition: match def.try_to_nav(self.db) {
-                    Some(x) => x.file_id == file_id && x.focus_or_full_range() == range,
-                    None => false,
+                is_definition: if let Some(x) = def.try_to_nav(self.db) {
+                    x.file_id == file_id && x.focus_or_full_range() == range
+                } else {
+                    false
                 },
             });
             result.tokens.push((range, id));
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index c4577e6350..03d513fe4d 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -330,7 +330,7 @@ fn traverse(
             }
         }
 
-        if macro_highlighter.highlight(element_to_highlight.clone()).is_some() {
+        if let Some(_) = macro_highlighter.highlight(element_to_highlight.clone()) {
             continue;
         }
 
diff --git a/crates/ide_assists/src/handlers/convert_while_to_loop.rs b/crates/ide_assists/src/handlers/convert_while_to_loop.rs
index 92dc2ee73a..2ecf646252 100644
--- a/crates/ide_assists/src/handlers/convert_while_to_loop.rs
+++ b/crates/ide_assists/src/handlers/convert_while_to_loop.rs
@@ -44,7 +44,7 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> O
     let cond = while_expr.condition()?;
 
     // Don't handle while let
-    if cond.pat().is_some() {
+    if let Some(_) = cond.pat() {
         return None;
     };
 
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index a367d97afe..2e91f8d4ab 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -827,9 +827,10 @@ impl FunctionBody {
         locals
             .map(|local| (local, local.source(ctx.db())))
             .filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
-            .filter_map(|(local, src)| match src.value {
-                Either::Left(src) => Some((local, src)),
-                Either::Right(_) => {
+            .filter_map(|(local, src)| {
+                if let Either::Left(src) = src.value {
+                    Some((local, src))
+                } else {
                     stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
                     None
                 }
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index 8968e0cd09..55e5da7fe7 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -76,11 +76,10 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
                 None => to_extract.syntax().text_range(),
             };
 
-            match anchor {
-                Anchor::Before(_) | Anchor::Replace(_) => {
-                    format_to!(buf, "let {} = ", var_name)
-                }
-                Anchor::WrapInBlock(_) => format_to!(buf, "{{ let {} = ", var_name),
+            if let Anchor::WrapInBlock(_) = anchor {
+                format_to!(buf, "{{ let {} = ", var_name);
+            } else {
+                format_to!(buf, "let {} = ", var_name);
             };
             format_to!(buf, "{}", to_extract.syntax());
 
diff --git a/crates/ide_assists/src/handlers/generate_function.rs b/crates/ide_assists/src/handlers/generate_function.rs
index baf9563551..64e5c23f7c 100644
--- a/crates/ide_assists/src/handlers/generate_function.rs
+++ b/crates/ide_assists/src/handlers/generate_function.rs
@@ -192,9 +192,10 @@ impl FunctionTemplate {
             Some(cap) => {
                 let cursor = if self.should_focus_return_type {
                     // Focus the return type if there is one
-                    match self.ret_type {
-                        Some(ref ret_type) => ret_type.syntax(),
-                        None => self.tail_expr.syntax(),
+                    if let Some(ref ret_type) = self.ret_type {
+                        ret_type.syntax()
+                    } else {
+                        self.tail_expr.syntax()
                     }
                 } else {
                     self.tail_expr.syntax()
@@ -427,9 +428,10 @@ fn fn_args(
         arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
             Some(ty) => {
                 if !ty.is_empty() && ty.starts_with('&') {
-                    match useless_type_special_case("", &ty[1..].to_owned()) {
-                        Some((new_ty, _)) => new_ty,
-                        None => ty,
+                    if let Some((new_ty, _)) = useless_type_special_case("", &ty[1..].to_owned()) {
+                        new_ty
+                    } else {
+                        ty
                     }
                 } else {
                     ty
@@ -521,7 +523,11 @@ fn fn_arg_type(
         return None;
     }
 
-    ty.display_source_code(ctx.db(), target_module.into()).ok()
+    if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
+        Some(rendered)
+    } else {
+        None
+    }
 }
 
 /// Returns the position inside the current mod or file
@@ -554,14 +560,20 @@ fn next_space_for_fn_in_module(
 ) -> Option<(FileId, GeneratedFunctionTarget)> {
     let file = module_source.file_id.original_file(db);
     let assist_item = match &module_source.value {
-        hir::ModuleSource::SourceFile(it) => match it.items().last() {
-            Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()),
-            None => GeneratedFunctionTarget::BehindItem(it.syntax().clone()),
-        },
-        hir::ModuleSource::Module(it) => match it.item_list().and_then(|it| it.items().last()) {
-            Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()),
-            None => GeneratedFunctionTarget::InEmptyItemList(it.item_list()?.syntax().clone()),
-        },
+        hir::ModuleSource::SourceFile(it) => {
+            if let Some(last_item) = it.items().last() {
+                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
+            } else {
+                GeneratedFunctionTarget::BehindItem(it.syntax().clone())
+            }
+        }
+        hir::ModuleSource::Module(it) => {
+            if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
+                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
+            } else {
+                GeneratedFunctionTarget::InEmptyItemList(it.item_list()?.syntax().clone())
+            }
+        }
         hir::ModuleSource::BlockExpr(it) => {
             if let Some(last_item) =
                 it.statements().take_while(|stmt| matches!(stmt, ast::Stmt::Item(_))).last()
diff --git a/crates/ide_assists/src/handlers/inline_call.rs b/crates/ide_assists/src/handlers/inline_call.rs
index 509758a582..b3dc95df41 100644
--- a/crates/ide_assists/src/handlers/inline_call.rs
+++ b/crates/ide_assists/src/handlers/inline_call.rs
@@ -141,9 +141,10 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext) -> Opt
             for (file_id, refs) in usages.into_iter() {
                 inline_refs_for_file(file_id, refs);
             }
-            match current_file_usage {
-                Some(refs) => inline_refs_for_file(def_file, refs),
-                None => builder.edit_file(def_file),
+            if let Some(refs) = current_file_usage {
+                inline_refs_for_file(def_file, refs);
+            } else {
+                builder.edit_file(def_file);
             }
             if remove_def {
                 builder.delete(ast_func.syntax().text_range());
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
index d142397c24..8946ecfac7 100644
--- a/crates/ide_assists/src/handlers/pull_assignment_up.rs
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -127,9 +127,12 @@ impl<'a> AssignmentsCollector<'a> {
         }
     }
     fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
-        let last_expr = block.tail_expr().or_else(|| match block.statements().last()? {
-            ast::Stmt::ExprStmt(stmt) => stmt.expr(),
-            ast::Stmt::Item(_) | ast::Stmt::LetStmt(_) => None,
+        let last_expr = block.tail_expr().or_else(|| {
+            if let ast::Stmt::ExprStmt(stmt) = block.statements().last()? {
+                stmt.expr()
+            } else {
+                None
+            }
         })?;
 
         if let ast::Expr::BinExpr(expr) = last_expr {
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index 0b33acc39b..8a2ec8ba80 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -181,9 +181,10 @@ fn find_trait_method(
 fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
     let item_module_def = item.as_module_def()?;
 
-    match item_module_def {
-        hir::ModuleDef::Trait(trait_) => Some(trait_),
-        _ => item_module_def.as_assoc_item(db)?.containing_trait(db),
+    if let hir::ModuleDef::Trait(trait_) = item_module_def {
+        Some(trait_)
+    } else {
+        item_module_def.as_assoc_item(db)?.containing_trait(db)
     }
 }
 
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 3d4ab968fb..dfb22afc7e 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -250,10 +250,13 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
             };
             Some(make::expr_method_call(receiver, make::name_ref(method), arg_list))
         }
-        ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
-            ast::Expr::ParenExpr(parexpr) => parexpr.expr(),
-            _ => pe.expr(),
-        },
+        ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => {
+            if let ast::Expr::ParenExpr(parexpr) = pe.expr()? {
+                parexpr.expr()
+            } else {
+                pe.expr()
+            }
+        }
         ast::Expr::Literal(lit) => match lit.kind() {
             ast::LiteralKind::Bool(b) => match b {
                 true => Some(ast::Expr::Literal(make::expr_literal("false"))),
@@ -273,10 +276,13 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
     let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text());
 
     let pat_head = match pat {
-        ast::Pat::IdentPat(bind_pat) => match bind_pat.pat() {
-            Some(p) => first_node_text(&p),
-            None => return pat.syntax().text() == var.syntax().text(),
-        },
+        ast::Pat::IdentPat(bind_pat) => {
+            if let Some(p) = bind_pat.pat() {
+                first_node_text(&p)
+            } else {
+                return pat.syntax().text() == var.syntax().text();
+            }
+        }
         pat => first_node_text(pat),
     };
 
diff --git a/crates/ide_assists/src/utils/suggest_name.rs b/crates/ide_assists/src/utils/suggest_name.rs
index 74523234b0..35e2300196 100644
--- a/crates/ide_assists/src/utils/suggest_name.rs
+++ b/crates/ide_assists/src/utils/suggest_name.rs
@@ -144,9 +144,10 @@ fn is_valid_name(name: &str) -> bool {
 fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
     let ident = method.name_ref().and_then(|it| it.ident_token());
 
-    match ident {
-        Some(ident) => USELESS_METHODS.contains(&ident.text()),
-        None => false,
+    if let Some(ident) = ident {
+        USELESS_METHODS.contains(&ident.text())
+    } else {
+        false
     }
 }
 
diff --git a/crates/ide_completion/Cargo.toml b/crates/ide_completion/Cargo.toml
index 8d91001560..0d4413978d 100644
--- a/crates/ide_completion/Cargo.toml
+++ b/crates/ide_completion/Cargo.toml
@@ -14,6 +14,7 @@ itertools = "0.10.0"
 rustc-hash = "1.1.0"
 either = "1.6.1"
 once_cell = "1.7"
+smallvec = "1.4"
 
 stdx = { path = "../stdx", version = "0.0.0" }
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs
index 55c49c2322..b74030c173 100644
--- a/crates/ide_completion/src/completions/postfix.rs
+++ b/crates/ide_completion/src/completions/postfix.rs
@@ -2,8 +2,9 @@
 
 mod format_like;
 
+use hir::Documentation;
 use ide_db::{
-    helpers::{FamousDefs, SnippetCap},
+    helpers::{insert_use::ImportScope, FamousDefs, SnippetCap},
     ty_filter::TryEnum,
 };
 use syntax::{
@@ -56,6 +57,10 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
 
     let postfix_snippet = build_postfix_snippet_builder(ctx, cap, &dot_receiver);
 
+    if !ctx.config.snippets.is_empty() {
+        add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
+    }
+
     let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
     if let Some(try_enum) = &try_enum {
         match try_enum {
@@ -218,13 +223,40 @@ fn build_postfix_snippet_builder<'a>(
     }
 }
 
+fn add_custom_postfix_completions(
+    acc: &mut Completions,
+    ctx: &CompletionContext,
+    postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
+    receiver_text: &str,
+) -> Option<()> {
+    let import_scope =
+        ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
+    ctx.config.postfix_snippets().filter(|(_, snip)| snip.is_expr()).for_each(
+        |(trigger, snippet)| {
+            let imports = match snippet.imports(ctx, &import_scope) {
+                Some(imports) => imports,
+                None => return,
+            };
+            let body = snippet.postfix_snippet(&receiver_text);
+            let mut builder =
+                postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
+            builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+            for import in imports.into_iter() {
+                builder.add_import(import);
+            }
+            builder.add_to(acc);
+        },
+    );
+    None
+}
+
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
 
     use crate::{
-        tests::{check_edit, filtered_completion_list},
-        CompletionKind,
+        tests::{check_edit, check_edit_with_config, filtered_completion_list, TEST_CONFIG},
+        CompletionConfig, CompletionKind, Snippet,
     };
 
     fn check(ra_fixture: &str, expect: Expect) {
@@ -442,6 +474,34 @@ fn main() {
         )
     }
 
+    #[test]
+    fn custom_postfix_completion() {
+        check_edit_with_config(
+            CompletionConfig {
+                snippets: vec![Snippet::new(
+                    &[],
+                    &["break".into()],
+                    &["ControlFlow::Break(${receiver})".into()],
+                    "",
+                    &["core::ops::ControlFlow".into()],
+                    crate::SnippetScope::Expr,
+                )
+                .unwrap()],
+                ..TEST_CONFIG
+            },
+            "break",
+            r#"
+//- minicore: try
+fn main() { 42.$0 }
+"#,
+            r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(42) }
+"#,
+        );
+    }
+
     #[test]
     fn postfix_completion_for_format_like_strings() {
         check_edit(
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index a896a759ab..a0e5f56129 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -1,11 +1,12 @@
 //! This file provides snippet completions, like `pd` => `eprintln!(...)`.
 
-use ide_db::helpers::SnippetCap;
+use hir::Documentation;
+use ide_db::helpers::{insert_use::ImportScope, SnippetCap};
 use syntax::T;
 
 use crate::{
     context::PathCompletionContext, item::Builder, CompletionContext, CompletionItem,
-    CompletionItemKind, CompletionKind, Completions,
+    CompletionItemKind, CompletionKind, Completions, SnippetScope,
 };
 
 fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
@@ -29,6 +30,10 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
         None => return,
     };
 
+    if !ctx.config.snippets.is_empty() {
+        add_custom_completions(acc, ctx, cap, SnippetScope::Expr);
+    }
+
     if can_be_stmt {
         snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
         snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
@@ -52,6 +57,10 @@ pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte
         None => return,
     };
 
+    if !ctx.config.snippets.is_empty() {
+        add_custom_completions(acc, ctx, cap, SnippetScope::Item);
+    }
+
     let mut item = snippet(
         ctx,
         cap,
@@ -86,3 +95,66 @@ fn ${1:feature}() {
     let item = snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}");
     item.add_to(acc);
 }
+
+fn add_custom_completions(
+    acc: &mut Completions,
+    ctx: &CompletionContext,
+    cap: SnippetCap,
+    scope: SnippetScope,
+) -> Option<()> {
+    let import_scope =
+        ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
+    ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
+        |(trigger, snip)| {
+            let imports = match snip.imports(ctx, &import_scope) {
+                Some(imports) => imports,
+                None => return,
+            };
+            let body = snip.snippet();
+            let mut builder = snippet(ctx, cap, &trigger, &body);
+            builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+            for import in imports.into_iter() {
+                builder.add_import(import);
+            }
+            builder.detail(snip.description.as_deref().unwrap_or_default());
+            builder.add_to(acc);
+        },
+    );
+    None
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        tests::{check_edit_with_config, TEST_CONFIG},
+        CompletionConfig, Snippet,
+    };
+
+    #[test]
+    fn custom_snippet_completion() {
+        check_edit_with_config(
+            CompletionConfig {
+                snippets: vec![Snippet::new(
+                    &["break".into()],
+                    &[],
+                    &["ControlFlow::Break(())".into()],
+                    "",
+                    &["core::ops::ControlFlow".into()],
+                    crate::SnippetScope::Expr,
+                )
+                .unwrap()],
+                ..TEST_CONFIG
+            },
+            "break",
+            r#"
+//- minicore: try
+fn main() { $0 }
+"#,
+            r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(()) }
+"#,
+        );
+    }
+}
diff --git a/crates/ide_completion/src/config.rs b/crates/ide_completion/src/config.rs
index c300ce887b..c659b4455a 100644
--- a/crates/ide_completion/src/config.rs
+++ b/crates/ide_completion/src/config.rs
@@ -6,6 +6,8 @@
 
 use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
 
+use crate::snippet::Snippet;
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct CompletionConfig {
     pub enable_postfix_completions: bool,
@@ -15,4 +17,18 @@ pub struct CompletionConfig {
     pub add_call_argument_snippets: bool,
     pub snippet_cap: Option<SnippetCap>,
     pub insert_use: InsertUseConfig,
+    pub snippets: Vec<Snippet>,
+}
+
+impl CompletionConfig {
+    pub fn postfix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+        self.snippets.iter().flat_map(|snip| {
+            snip.postfix_triggers.iter().map(move |trigger| (trigger.as_str(), snip))
+        })
+    }
+    pub fn prefix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+        self.snippets.iter().flat_map(|snip| {
+            snip.prefix_triggers.iter().map(move |trigger| (trigger.as_str(), snip))
+        })
+    }
 }
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index d6e15e6af7..3d59156d00 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -509,9 +509,10 @@ impl<'a> CompletionContext<'a> {
                             .and_then(|pat| self.sema.type_of_pat(&pat))
                             .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)))
                             .map(TypeInfo::original);
-                        let name = match it.pat() {
-                            Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
-                            Some(_) | None => None,
+                        let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
+                            ident.name().map(NameOrNameRef::Name)
+                        } else {
+                            None
                         };
 
                         (ty, name)
@@ -868,7 +869,8 @@ mod tests {
 
     fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
         let (db, pos) = position(ra_fixture);
-        let completion_context = CompletionContext::new(&db, pos, &TEST_CONFIG).unwrap();
+        let config = TEST_CONFIG;
+        let completion_context = CompletionContext::new(&db, pos, &config).unwrap();
 
         let ty = completion_context
             .expected_type
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 2bc69d5657..4c75bd6900 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -11,6 +11,7 @@ use ide_db::{
     },
     SymbolKind,
 };
+use smallvec::SmallVec;
 use stdx::{format_to, impl_from, never};
 use syntax::{algo, TextRange};
 use text_edit::TextEdit;
@@ -76,7 +77,7 @@ pub struct CompletionItem {
     ref_match: Option<Mutability>,
 
     /// The import data to add to completion's edits.
-    import_to_add: Option<ImportEdit>,
+    import_to_add: SmallVec<[ImportEdit; 1]>,
 }
 
 // We use custom debug for CompletionItem to make snapshot tests more readable.
@@ -305,7 +306,7 @@ impl CompletionItem {
             trigger_call_info: None,
             relevance: CompletionRelevance::default(),
             ref_match: None,
-            import_to_add: None,
+            imports_to_add: Default::default(),
         }
     }
 
@@ -364,8 +365,8 @@ impl CompletionItem {
         self.ref_match.map(|mutability| (mutability, relevance))
     }
 
-    pub fn import_to_add(&self) -> Option<&ImportEdit> {
-        self.import_to_add.as_ref()
+    pub fn imports_to_add(&self) -> &[ImportEdit] {
+        &self.import_to_add
     }
 }
 
@@ -398,7 +399,7 @@ impl ImportEdit {
 pub(crate) struct Builder {
     source_range: TextRange,
     completion_kind: CompletionKind,
-    import_to_add: Option<ImportEdit>,
+    imports_to_add: SmallVec<[ImportEdit; 1]>,
     trait_name: Option<String>,
     label: String,
     insert_text: Option<String>,
@@ -422,14 +423,13 @@ impl Builder {
         let mut lookup = self.lookup;
         let mut insert_text = self.insert_text;
 
-        if let Some(original_path) = self
-            .import_to_add
-            .as_ref()
-            .and_then(|import_edit| import_edit.import.original_path.as_ref())
-        {
-            lookup = lookup.or_else(|| Some(label.clone()));
-            insert_text = insert_text.or_else(|| Some(label.clone()));
-            format_to!(label, " (use {})", original_path)
+        if let [import_edit] = &*self.imports_to_add {
+            // snippets can have multiple imports, but normal completions only have up to one
+            if let Some(original_path) = import_edit.import.original_path.as_ref() {
+                lookup = lookup.or_else(|| Some(label.clone()));
+                insert_text = insert_text.or_else(|| Some(label.clone()));
+                format_to!(label, " (use {})", original_path)
+            }
         } else if let Some(trait_name) = self.trait_name {
             insert_text = insert_text.or_else(|| Some(label.clone()));
             format_to!(label, " (as {})", trait_name)
@@ -456,7 +456,7 @@ impl Builder {
             trigger_call_info: self.trigger_call_info.unwrap_or(false),
             relevance: self.relevance,
             ref_match: self.ref_match,
-            import_to_add: self.import_to_add,
+            import_to_add: self.imports_to_add,
         }
     }
     pub(crate) fn lookup_by(&mut self, lookup: impl Into<String>) -> &mut Builder {
@@ -527,8 +527,8 @@ impl Builder {
         self.trigger_call_info = Some(true);
         self
     }
-    pub(crate) fn add_import(&mut self, import_to_add: Option<ImportEdit>) -> &mut Builder {
-        self.import_to_add = import_to_add;
+    pub(crate) fn add_import(&mut self, import_to_add: ImportEdit) -> &mut Builder {
+        self.imports_to_add.push(import_to_add);
         self
     }
     pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder {
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index f10f3772b1..251ddfa2fc 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -9,16 +9,19 @@ mod render;
 
 #[cfg(test)]
 mod tests;
+mod snippet;
 
 use completions::flyimport::position_for_import;
 use ide_db::{
     base_db::FilePosition,
     helpers::{
-        import_assets::{LocatedImport, NameToImport},
-        insert_use::ImportScope,
+        import_assets::NameToImport,
+        insert_use::{self, ImportScope},
+        mod_path_to_ast,
     },
     items_locator, RootDatabase,
 };
+use syntax::algo;
 use text_edit::TextEdit;
 
 use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
@@ -26,6 +29,7 @@ use crate::{completions::Completions, context::CompletionContext, item::Completi
 pub use crate::{
     config::CompletionConfig,
     item::{CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit},
+    snippet::{Snippet, SnippetScope},
 };
 
 //FIXME: split the following feature into fine-grained features.
@@ -173,31 +177,37 @@ pub fn resolve_completion_edits(
     db: &RootDatabase,
     config: &CompletionConfig,
     position: FilePosition,
-    full_import_path: &str,
-    imported_name: String,
+    imports: impl IntoIterator<Item = (String, String)>,
 ) -> Option<Vec<TextEdit>> {
+    let _p = profile::span("resolve_completion_edits");
     let ctx = CompletionContext::new(db, position, config)?;
     let position_for_import = position_for_import(&ctx, None)?;
     let scope = ImportScope::find_insert_use_container_with_macros(position_for_import, &ctx.sema)?;
 
     let current_module = ctx.sema.scope(position_for_import).module()?;
     let current_crate = current_module.krate();
+    let new_ast = scope.clone_for_update();
+    let mut import_insert = TextEdit::builder();
 
-    let (import_path, item_to_import) = items_locator::items_with_name(
-        &ctx.sema,
-        current_crate,
-        NameToImport::Exact(imported_name),
-        items_locator::AssocItemSearch::Include,
-        Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
-    )
-    .filter_map(|candidate| {
-        current_module
-            .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
-            .zip(Some(candidate))
-    })
-    .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
-    let import =
-        LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
+    // FIXME: lift out and make some tests here, this is ImportEdit::to_text_edit but changed to work with multiple edits
+    imports.into_iter().for_each(|(full_import_path, imported_name)| {
+        let items_with_name = items_locator::items_with_name(
+            &ctx.sema,
+            current_crate,
+            NameToImport::Exact(imported_name),
+            items_locator::AssocItemSearch::Include,
+            Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+        );
+        let import = items_with_name
+            .filter_map(|candidate| {
+                current_module.find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
+            })
+            .find(|mod_path| mod_path.to_string() == full_import_path);
+        if let Some(import_path) = import {
+            insert_use::insert_use(&new_ast, mod_path_to_ast(&import_path), &config.insert_use);
+        }
+    });
 
-    ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit])
+    algo::diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
+    Some(vec![import_insert.finish()])
 }
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index 62a5fac534..58443f566e 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -212,7 +212,10 @@ fn render_resolution_(
                 ctx.source_range(),
                 local_name.to_string(),
             );
-            item.kind(CompletionItemKind::UnresolvedReference).add_import(import_to_add);
+            item.kind(CompletionItemKind::UnresolvedReference);
+            if let Some(import_to_add) = import_to_add {
+                item.add_import(import_to_add);
+            }
             return Some(item.build());
         }
     };
@@ -258,9 +261,12 @@ fn render_resolution_(
         }
     }
     item.kind(kind)
-        .add_import(import_to_add)
         .set_documentation(scope_def_docs(ctx.db(), resolution))
         .set_deprecated(scope_def_is_deprecated(&ctx, resolution));
+
+    if let Some(import_to_add) = import_to_add {
+        item.add_import(import_to_add);
+    }
     Some(item.build())
 }
 
diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs
index d5cfd8bba4..2ba86eaa0a 100644
--- a/crates/ide_completion/src/render/enum_variant.rs
+++ b/crates/ide_completion/src/render/enum_variant.rs
@@ -68,9 +68,12 @@ impl<'a> EnumRender<'a> {
         item.kind(SymbolKind::Variant)
             .set_documentation(self.variant.docs(self.ctx.db()))
             .set_deprecated(self.ctx.is_deprecated(self.variant))
-            .add_import(import_to_add)
             .detail(self.detail());
 
+        if let Some(import_to_add) = import_to_add {
+            item.add_import(import_to_add);
+        }
+
         if self.variant_kind == hir::StructKind::Tuple {
             cov_mark::hit!(inserts_parens_for_tuple_enums);
             let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len());
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs
index 904624f9ab..4e4663b857 100644
--- a/crates/ide_completion/src/render/function.rs
+++ b/crates/ide_completion/src/render/function.rs
@@ -74,9 +74,10 @@ impl<'a> FunctionRender<'a> {
 
     fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
         let params = self.params();
-        let call = match &self.receiver {
-            Some(receiver) => format!("{}.{}", receiver, &self.name),
-            None => self.name.clone(),
+        let call = if let Some(receiver) = &self.receiver {
+            format!("{}.{}", receiver, &self.name)
+        } else {
+            self.name.clone()
         };
         let mut item =
             CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), call.clone());
@@ -97,7 +98,10 @@ impl<'a> FunctionRender<'a> {
             }
         }
 
-        item.add_import(import_to_add).lookup_by(self.name);
+        if let Some(import_to_add) = import_to_add {
+            item.add_import(import_to_add);
+        }
+        item.lookup_by(self.name);
 
         let ret_type = self.func.ret_type(self.ctx.db());
         item.set_relevance(CompletionRelevance {
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs
index d1b549df1b..196b667baa 100644
--- a/crates/ide_completion/src/render/macro_.rs
+++ b/crates/ide_completion/src/render/macro_.rs
@@ -51,9 +51,12 @@ impl<'a> MacroRender<'a> {
         item.kind(SymbolKind::Macro)
             .set_documentation(self.docs.clone())
             .set_deprecated(self.ctx.is_deprecated(self.macro_))
-            .add_import(import_to_add)
             .set_detail(self.detail());
 
+        if let Some(import_to_add) = import_to_add {
+            item.add_import(import_to_add);
+        }
+
         let needs_bang = !(self.ctx.completion.in_use_tree()
             || matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)));
         let has_parens = self.ctx.completion.path_call_kind().is_some();
diff --git a/crates/ide_completion/src/render/pattern.rs b/crates/ide_completion/src/render/pattern.rs
index 521296fd9b..306e418e91 100644
--- a/crates/ide_completion/src/render/pattern.rs
+++ b/crates/ide_completion/src/render/pattern.rs
@@ -63,9 +63,10 @@ fn build_completion(
         .set_documentation(ctx.docs(def))
         .set_deprecated(ctx.is_deprecated(def))
         .detail(&pat);
-    match ctx.snippet_cap() {
-        Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
-        None => item.insert_text(pat),
+    if let Some(snippet_cap) = ctx.snippet_cap() {
+        item.insert_snippet(snippet_cap, pat);
+    } else {
+        item.insert_text(pat);
     };
     item.build()
 }
diff --git a/crates/ide_completion/src/render/struct_literal.rs b/crates/ide_completion/src/render/struct_literal.rs
index 810b51effd..68871a4671 100644
--- a/crates/ide_completion/src/render/struct_literal.rs
+++ b/crates/ide_completion/src/render/struct_literal.rs
@@ -38,9 +38,10 @@ fn build_completion(
         .set_documentation(ctx.docs(def))
         .set_deprecated(ctx.is_deprecated(def))
         .detail(&literal);
-    match ctx.snippet_cap() {
-        Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
-        None => item.insert_text(literal),
+    if let Some(snippet_cap) = ctx.snippet_cap() {
+        item.insert_snippet(snippet_cap, literal);
+    } else {
+        item.insert_text(literal);
     };
     item.build()
 }
diff --git a/crates/ide_completion/src/snippet.rs b/crates/ide_completion/src/snippet.rs
new file mode 100644
index 0000000000..d527f3aef6
--- /dev/null
+++ b/crates/ide_completion/src/snippet.rs
@@ -0,0 +1,176 @@
+//! User (postfix)-snippet definitions.
+//!
+//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`].
+
+// Feature: User Snippet Completions
+//
+// rust-analyzer allows the user to define custom (postfix)-snippets that may depend on items to be accessible for the current scope to be applicable.
+//
+// A custom snippet can be defined by adding it to the `rust-analyzer.completion.snippets` object respectively.
+//
+// [source,json]
+// ----
+// {
+//   "rust-analyzer.completion.snippets": {
+//     "thread spawn": {
+//       "prefix": ["spawn", "tspawn"],
+//       "body": [
+//         "thread::spawn(move || {",
+//         "\t$0",
+//         ")};",
+//       ],
+//       "description": "Insert a thread::spawn call",
+//       "requires": "std::thread",
+//       "scope": "expr",
+//     }
+//   }
+// }
+// ----
+//
+// In the example above:
+//
+// * `"thread spawn"` is the name of the snippet.
+//
+// * `prefix` defines one or more trigger words that will trigger the snippets completion.
+// Using `postfix` will instead create a postfix snippet.
+//
+// * `body` is one or more lines of content joined via newlines for the final output.
+//
+// * `description` is an optional description of the snippet, if unset the snippet name will be used.
+//
+// * `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered.
+// On failure of resolution the snippet won't be applicable, otherwise the snippet will insert an import for the items on insertion if
+// the items aren't yet in scope.
+//
+// * `scope` is an optional filter for when the snippet should be applicable. Possible values are:
+// ** for Snippet-Scopes: `expr`, `item` (default: `item`)
+// ** for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`)
+//
+// The `body` field also has access to placeholders as visible in the example as `$0`.
+// These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1,
+// with `$0` being a special case that always comes last.
+//
+// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or nothing in case of normal snippets.
+// It does not act as a tabstop.
+use ide_db::helpers::{import_assets::LocatedImport, insert_use::ImportScope};
+use itertools::Itertools;
+use syntax::ast;
+
+use crate::{context::CompletionContext, ImportEdit};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SnippetScope {
+    Item,
+    Expr,
+    Type,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Snippet {
+    pub postfix_triggers: Box<[String]>,
+    pub prefix_triggers: Box<[String]>,
+    pub scope: SnippetScope,
+    snippet: String,
+    pub description: Option<String>,
+    pub requires: Box<[String]>,
+}
+
+impl Snippet {
+    pub fn new(
+        prefix_triggers: &[String],
+        postfix_triggers: &[String],
+        snippet: &[String],
+        description: &str,
+        requires: &[String],
+        scope: SnippetScope,
+    ) -> Option<Self> {
+        let (snippet, description) = validate_snippet(snippet, description, requires)?;
+        Some(Snippet {
+            // Box::into doesn't work as that has a Copy bound 😒
+            postfix_triggers: postfix_triggers.iter().cloned().collect(),
+            prefix_triggers: prefix_triggers.iter().cloned().collect(),
+            scope,
+            snippet,
+            description,
+            requires: requires.iter().cloned().collect(),
+        })
+    }
+
+    /// Returns None if the required items do not resolve.
+    pub(crate) fn imports(
+        &self,
+        ctx: &CompletionContext,
+        import_scope: &ImportScope,
+    ) -> Option<Vec<ImportEdit>> {
+        import_edits(ctx, import_scope, &self.requires)
+    }
+
+    pub fn snippet(&self) -> String {
+        self.snippet.replace("${receiver}", "")
+    }
+
+    pub fn postfix_snippet(&self, receiver: &str) -> String {
+        self.snippet.replace("${receiver}", receiver)
+    }
+
+    pub fn is_item(&self) -> bool {
+        self.scope == SnippetScope::Item
+    }
+
+    pub fn is_expr(&self) -> bool {
+        self.scope == SnippetScope::Expr
+    }
+}
+
+fn import_edits(
+    ctx: &CompletionContext,
+    import_scope: &ImportScope,
+    requires: &[String],
+) -> Option<Vec<ImportEdit>> {
+    let resolve = |import| {
+        let path = ast::Path::parse(import).ok()?;
+        let item = match ctx.scope.speculative_resolve(&path)? {
+            hir::PathResolution::Macro(mac) => mac.into(),
+            hir::PathResolution::Def(def) => def.into(),
+            _ => return None,
+        };
+        let path = ctx.scope.module()?.find_use_path_prefixed(
+            ctx.db,
+            item,
+            ctx.config.insert_use.prefix_kind,
+        )?;
+        Some((path.len() > 1).then(|| ImportEdit {
+            import: LocatedImport::new(path.clone(), item, item, None),
+            scope: import_scope.clone(),
+        }))
+    };
+    let mut res = Vec::with_capacity(requires.len());
+    for import in requires {
+        match resolve(import) {
+            Some(first) => res.extend(first),
+            None => return None,
+        }
+    }
+    Some(res)
+}
+
+fn validate_snippet(
+    snippet: &[String],
+    description: &str,
+    requires: &[String],
+) -> Option<(String, Option<String>)> {
+    // validate that these are indeed simple paths
+    // we can't save the paths unfortunately due to them not being Send+Sync
+    if requires.iter().any(|path| match ast::Path::parse(path) {
+        Ok(path) => path.segments().any(|seg| {
+            !matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
+                || seg.generic_arg_list().is_some()
+        }),
+        Err(_) => true,
+    }) {
+        return None;
+    }
+    let snippet = snippet.iter().join("\n");
+    let description = if description.is_empty() { None } else { Some(description.to_owned()) };
+    Some((snippet, description))
+}
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs
index 4fb5c770e5..fcf1a654b0 100644
--- a/crates/ide_db/src/rename.rs
+++ b/crates/ide_db/src/rename.rs
@@ -318,7 +318,7 @@ pub fn source_edit_from_references(
 }
 
 fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: &str) -> bool {
-    if ast::RecordPatField::for_field_name(name).is_some() {
+    if let Some(_) = ast::RecordPatField::for_field_name(name) {
         if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
             cov_mark::hit!(rename_record_pat_field_name_split);
             // Foo { ref mut field } -> Foo { new_name: ref mut field }
diff --git a/crates/ide_ssr/src/resolving.rs b/crates/ide_ssr/src/resolving.rs
index 9cdd270b80..df20ab375d 100644
--- a/crates/ide_ssr/src/resolving.rs
+++ b/crates/ide_ssr/src/resolving.rs
@@ -47,9 +47,10 @@ impl ResolvedRule {
     ) -> Result<ResolvedRule, SsrError> {
         let resolver =
             Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
-        let resolved_template = match rule.template {
-            Some(template) => Some(resolver.resolve_pattern_tree(template)?),
-            None => None,
+        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)?,
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 991955fca6..3d0ad6735e 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -67,11 +67,11 @@ pub(crate) mod entry_points {
     }
 
     pub(crate) fn stmt(p: &mut Parser) {
-        expressions::stmt(p, expressions::StmtWithSemi::No, true);
+        expressions::stmt(p, expressions::StmtWithSemi::No, true)
     }
 
     pub(crate) fn stmt_optional_semi(p: &mut Parser) {
-        expressions::stmt(p, expressions::StmtWithSemi::Optional, false);
+        expressions::stmt(p, expressions::StmtWithSemi::Optional, false)
     }
 
     pub(crate) fn visibility(p: &mut Parser) {
@@ -84,7 +84,7 @@ pub(crate) mod entry_points {
     }
 
     pub(crate) fn item(p: &mut Parser) {
-        items::item_or_macro(p, true);
+        items::item_or_macro(p, true)
     }
 
     pub(crate) fn macro_items(p: &mut Parser) {
@@ -109,7 +109,7 @@ pub(crate) mod entry_points {
     }
 
     pub(crate) fn attr(p: &mut Parser) {
-        attributes::outer_attrs(p);
+        attributes::outer_attrs(p)
     }
 }
 
@@ -128,7 +128,8 @@ pub(crate) fn reparser(
         EXTERN_ITEM_LIST => items::extern_item_list,
         TOKEN_TREE if first_child? == T!['{'] => items::token_tree,
         ASSOC_ITEM_LIST => match parent? {
-            IMPL | TRAIT => items::assoc_item_list,
+            IMPL => items::assoc_item_list,
+            TRAIT => items::assoc_item_list,
             _ => return None,
         },
         ITEM_LIST => items::item_list,
@@ -245,7 +246,7 @@ fn name_r(p: &mut Parser, recovery: TokenSet) {
 }
 
 fn name(p: &mut Parser) {
-    name_r(p, TokenSet::EMPTY);
+    name_r(p, TokenSet::EMPTY)
 }
 
 fn name_ref(p: &mut Parser) {
diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs
index 1efffca515..574629f31a 100644
--- a/crates/parser/src/grammar/attributes.rs
+++ b/crates/parser/src/grammar/attributes.rs
@@ -2,13 +2,13 @@ use super::*;
 
 pub(super) fn inner_attrs(p: &mut Parser) {
     while p.at(T![#]) && p.nth(1) == T![!] {
-        attr(p, true);
+        attr(p, true)
     }
 }
 
 pub(super) fn outer_attrs(p: &mut Parser) {
     while p.at(T![#]) {
-        attr(p, false);
+        attr(p, false)
     }
 }
 
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index aa171674ed..645101e2f7 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -139,7 +139,7 @@ pub(super) fn expr_block_contents(p: &mut Parser) {
             continue;
         }
 
-        stmt(p, StmtWithSemi::Yes, false);
+        stmt(p, StmtWithSemi::Yes, false)
     }
 }
 
@@ -311,7 +311,7 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)>
         _ => {
             // test full_range_expr
             // fn foo() { xs[..]; }
-            for op in [T![..=], T![..]] {
+            for &op in [T![..=], T![..]].iter() {
                 if p.at(op) {
                     m = p.start();
                     p.bump(op);
@@ -468,12 +468,12 @@ fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
     let m = lhs.precede(p);
     p.bump(T![.]);
     if p.at(IDENT) || p.at(INT_NUMBER) {
-        name_ref_or_index(p);
+        name_ref_or_index(p)
     } else if p.at(FLOAT_NUMBER) {
         // FIXME: How to recover and instead parse INT + T![.]?
         p.bump_any();
     } else {
-        p.error("expected field name or number");
+        p.error("expected field name or number")
     }
     m.complete(p, FIELD_EXPR)
 }
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 7075ae297f..bfa02aa222 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -374,7 +374,7 @@ fn match_expr(p: &mut Parser) -> CompletedMarker {
     if p.at(T!['{']) {
         match_arm_list(p);
     } else {
-        p.error("expected `{`");
+        p.error("expected `{`")
     }
     m.complete(p, MATCH_EXPR)
 }
@@ -602,7 +602,7 @@ fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
     if p.at(T!['{']) {
         stmt_list(p);
     } else {
-        p.error("expected a block");
+        p.error("expected a block")
     }
     m.complete(p, BLOCK_EXPR)
 }
@@ -639,7 +639,7 @@ fn meta_var_expr(p: &mut Parser) -> CompletedMarker {
         }
         _ => {
             while !p.at(R_DOLLAR) {
-                p.bump_any();
+                p.bump_any()
             }
             p.bump(R_DOLLAR);
             m.complete(p, ERROR)
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index 00ccbf5010..5414b3b20d 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -34,7 +34,7 @@ fn generic_param(p: &mut Parser) {
         T![const] => const_param(p, m),
         _ => {
             m.abandon(p);
-            p.err_and_bump("expected type parameter");
+            p.err_and_bump("expected type parameter")
         }
     }
 }
@@ -62,7 +62,7 @@ fn type_param(p: &mut Parser, m: Marker) {
         // test type_param_default
         // struct S<T = i32>;
         p.bump(T![=]);
-        types::type_(p);
+        types::type_(p)
     }
     m.complete(p, TYPE_PARAM);
 }
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 9de9afde5d..517da6e95c 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -20,7 +20,7 @@ use super::*;
 pub(super) fn mod_contents(p: &mut Parser, stop_on_r_curly: bool) {
     attributes::inner_attrs(p);
     while !p.at(EOF) && !(p.at(T!['}']) && stop_on_r_curly) {
-        item_or_macro(p, stop_on_r_curly);
+        item_or_macro(p, stop_on_r_curly)
     }
 }
 
@@ -165,7 +165,7 @@ pub(super) fn opt_item(p: &mut Parser, m: Marker) -> Result<(), Marker> {
                     p.bump_remap(T![default]);
                     p.bump(T![async]);
                     if is_unsafe {
-                        p.bump(T![unsafe]);
+                        p.bump(T![unsafe])
                     }
                     has_mods = true;
                 }
@@ -404,7 +404,7 @@ fn fn_(p: &mut Parser, m: Marker) {
         // trait T { fn foo(); }
         p.bump(T![;]);
     } else {
-        expressions::block_expr(p);
+        expressions::block_expr(p)
     }
     m.complete(p, FN);
 }
diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs
index 42ebecc6d6..e4b1116958 100644
--- a/crates/parser/src/grammar/items/adt.rs
+++ b/crates/parser/src/grammar/items/adt.rs
@@ -58,7 +58,7 @@ pub(super) fn enum_(p: &mut Parser, m: Marker) {
     if p.at(T!['{']) {
         variant_list(p);
     } else {
-        p.error("expected `{`");
+        p.error("expected `{`")
     }
     m.complete(p, ENUM);
 }
diff --git a/crates/parser/src/grammar/items/consts.rs b/crates/parser/src/grammar/items/consts.rs
index 98064cd98a..93ba7d05fe 100644
--- a/crates/parser/src/grammar/items/consts.rs
+++ b/crates/parser/src/grammar/items/consts.rs
@@ -4,12 +4,12 @@ use super::*;
 // const C: u32 = 92;
 pub(super) fn konst(p: &mut Parser, m: Marker) {
     p.bump(T![const]);
-    const_or_static(p, m, true);
+    const_or_static(p, m, true)
 }
 
 pub(super) fn static_(p: &mut Parser, m: Marker) {
     p.bump(T![static]);
-    const_or_static(p, m, false);
+    const_or_static(p, m, false)
 }
 
 fn const_or_static(p: &mut Parser, m: Marker, is_const: bool) {
@@ -27,7 +27,7 @@ fn const_or_static(p: &mut Parser, m: Marker, is_const: bool) {
     if p.at(T![:]) {
         types::ascription(p);
     } else {
-        p.error("missing type for `const` or `static`");
+        p.error("missing type for `const` or `static`")
     }
     if p.eat(T![=]) {
         expressions::expr(p);
diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs
index a000f515cc..8b03ade81f 100644
--- a/crates/parser/src/grammar/params.rs
+++ b/crates/parser/src/grammar/params.rs
@@ -6,21 +6,21 @@ use super::*;
 // fn c(x: i32, ) {}
 // fn d(x: i32, y: ()) {}
 pub(super) fn param_list_fn_def(p: &mut Parser) {
-    list_(p, Flavor::FnDef);
+    list_(p, Flavor::FnDef)
 }
 
 // test param_list_opt_patterns
 // fn foo<F: FnMut(&mut Foo<'a>)>(){}
 pub(super) fn param_list_fn_trait(p: &mut Parser) {
-    list_(p, Flavor::FnTrait);
+    list_(p, Flavor::FnTrait)
 }
 
 pub(super) fn param_list_fn_ptr(p: &mut Parser) {
-    list_(p, Flavor::FnPointer);
+    list_(p, Flavor::FnPointer)
 }
 
 pub(super) fn param_list_closure(p: &mut Parser) {
-    list_(p, Flavor::Closure);
+    list_(p, Flavor::Closure)
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -104,13 +104,13 @@ fn param(p: &mut Parser, m: Marker, flavor: Flavor) -> Variadic {
         Flavor::FnDef => {
             patterns::pattern(p);
             if variadic_param(p) {
-                res = Variadic(true);
+                res = Variadic(true)
             } else if p.at(T![:]) {
-                types::ascription(p);
+                types::ascription(p)
             } else {
                 // test_err missing_fn_param_type
                 // fn f(x y: i32, z, t: i32) {}
-                p.error("missing type for function parameter");
+                p.error("missing type for function parameter")
             }
         }
         // test value_parameters_no_patterns
@@ -128,11 +128,11 @@ fn param(p: &mut Parser, m: Marker, flavor: Flavor) -> Variadic {
             if (p.at(IDENT) || p.at(UNDERSCORE)) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) {
                 patterns::pattern_single(p);
                 if variadic_param(p) {
-                    res = Variadic(true);
+                    res = Variadic(true)
                 } else if p.at(T![:]) {
-                    types::ascription(p);
+                    types::ascription(p)
                 } else {
-                    p.error("missing type for function parameter");
+                    p.error("missing type for function parameter")
                 }
             } else {
                 types::type_(p);
diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs
index 0cc0ed31aa..05a52c984a 100644
--- a/crates/parser/src/grammar/paths.rs
+++ b/crates/parser/src/grammar/paths.rs
@@ -16,15 +16,15 @@ pub(super) fn is_use_path_start(p: &Parser) -> bool {
 }
 
 pub(super) fn use_path(p: &mut Parser) {
-    path(p, Mode::Use);
+    path(p, Mode::Use)
 }
 
 pub(crate) fn type_path(p: &mut Parser) {
-    path(p, Mode::Type);
+    path(p, Mode::Type)
 }
 
 pub(super) fn expr_path(p: &mut Parser) {
-    path(p, Mode::Expr);
+    path(p, Mode::Expr)
 }
 
 pub(crate) fn type_path_for_qualifier(p: &mut Parser, qual: CompletedMarker) -> CompletedMarker {
@@ -117,7 +117,7 @@ fn opt_path_type_args(p: &mut Parser, mode: Mode) {
                 params::param_list_fn_trait(p);
                 opt_ret_type(p);
             } else {
-                generic_args::opt_generic_arg_list(p, false);
+                generic_args::opt_generic_arg_list(p, false)
             }
         }
         Mode::Expr => generic_args::opt_generic_arg_list(p, true),
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index 5fe1a7b13b..81e2051abb 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -19,7 +19,7 @@ pub(crate) fn pattern(p: &mut Parser) {
 
 /// Parses a pattern list separated by pipes `|`.
 pub(super) fn pattern_top(p: &mut Parser) {
-    pattern_top_r(p, PAT_RECOVERY_SET);
+    pattern_top_r(p, PAT_RECOVERY_SET)
 }
 
 pub(crate) fn pattern_single(p: &mut Parser) {
@@ -69,23 +69,18 @@ fn pattern_single_r(p: &mut Parser, recovery_set: TokenSet) {
         //         200 .. 301 => (),
         //         302 .. => (),
         //     }
-        //
-        //     match Some(10 as u8) {
-        //         Some(0) | None => (),
-        //         Some(1..) => ()
-        //     }
         // }
 
         // FIXME: support half_open_range_patterns (`..=2`),
         // exclusive_range_pattern (`..5`) with missing lhs
-        for range_op in [T![...], T![..=], T![..]] {
+        for &range_op in [T![...], T![..=], T![..]].iter() {
             if p.at(range_op) {
                 let m = lhs.precede(p);
                 p.bump(range_op);
 
-                // `0 .. =>` or `let 0 .. =` or `Some(0 .. )`
-                //       ^                ^                ^
-                if p.at(T![=]) | p.at(T![')']) {
+                // `0 .. =>` or `let 0 .. =`
+                //       ^                ^
+                if p.at(T![=]) {
                     // test half_open_range_pat
                     // fn f() { let 0 .. = 1u32; }
                 } else {
diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs
index 1a6fb651cb..c2aa9ffc3b 100644
--- a/crates/parser/src/grammar/types.rs
+++ b/crates/parser/src/grammar/types.rs
@@ -57,7 +57,7 @@ fn type_with_bounds_cond(p: &mut Parser, allow_bounds: bool) {
 pub(super) fn ascription(p: &mut Parser) {
     assert!(p.at(T![:]));
     p.bump(T![:]);
-    type_(p);
+    type_(p)
 }
 
 fn paren_or_tuple_type(p: &mut Parser) {
@@ -204,7 +204,7 @@ fn fn_ptr_type(p: &mut Parser) {
     if p.at(T!['(']) {
         params::param_list_fn_ptr(p);
     } else {
-        p.error("expected parameters");
+        p.error("expected parameters")
     }
     // test fn_pointer_type_with_ret
     // type F = fn() -> ();
@@ -274,7 +274,7 @@ fn dyn_trait_type(p: &mut Parser) {
 // type C = self::Foo;
 // type D = super::Foo;
 pub(super) fn path_type(p: &mut Parser) {
-    path_type_(p, true);
+    path_type_(p, true)
 }
 
 // test macro_call_type
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index 89dfea52f8..1f9961bb92 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -177,7 +177,7 @@ impl<'t> Parser<'t> {
         if kind == EOF {
             return;
         }
-        self.do_bump(kind, 1);
+        self.do_bump(kind, 1)
     }
 
     /// Advances the parser by one token, remapping its kind.
@@ -200,7 +200,7 @@ impl<'t> Parser<'t> {
     /// does.
     pub(crate) fn error<T: Into<String>>(&mut self, message: T) {
         let msg = ParseError(Box::new(message.into()));
-        self.push_event(Event::Error { msg });
+        self.push_event(Event::Error { msg })
     }
 
     /// Consume the next token if it is `kind` or emit an error
@@ -258,7 +258,7 @@ impl<'t> Parser<'t> {
     }
 
     fn push_event(&mut self, event: Event) {
-        self.events.push(event);
+        self.events.push(event)
     }
 }
 
diff --git a/crates/parser/src/token_set.rs b/crates/parser/src/token_set.rs
index cd4894c1e8..a68f0144e7 100644
--- a/crates/parser/src/token_set.rs
+++ b/crates/parser/src/token_set.rs
@@ -14,7 +14,7 @@ impl TokenSet {
         let mut i = 0;
         while i < kinds.len() {
             res |= mask(kinds[i]);
-            i += 1;
+            i += 1
         }
         TokenSet(res)
     }
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
index 41731c8d27..90909f4e8e 100644
--- a/crates/paths/src/lib.rs
+++ b/crates/paths/src/lib.rs
@@ -271,7 +271,7 @@ impl RelPath {
 /// Taken from <https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85>
 fn normalize_path(path: &Path) -> PathBuf {
     let mut components = path.components().peekable();
-    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
+    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
         components.next();
         PathBuf::from(c.as_os_str())
     } else {
diff --git a/crates/proc_macro_api/src/msg/flat.rs b/crates/proc_macro_api/src/msg/flat.rs
index 8437444e18..3201394f7f 100644
--- a/crates/proc_macro_api/src/msg/flat.rs
+++ b/crates/proc_macro_api/src/msg/flat.rs
@@ -246,7 +246,7 @@ impl<'a> Writer<'a> {
 
     fn enqueue(&mut self, subtree: &'a tt::Subtree) -> u32 {
         let idx = self.subtree.len();
-        let delimiter_id = subtree.delimiter.map_or(TokenId::unspecified(), |it| it.id);
+        let delimiter_id = subtree.delimiter.map(|it| it.id).unwrap_or_else(TokenId::unspecified);
         let delimiter_kind = subtree.delimiter.map(|it| it.kind);
         self.subtree.push(SubtreeRepr { id: delimiter_id, kind: delimiter_kind, tt: [!0, !0] });
         self.work.push_back((idx, subtree));
@@ -320,7 +320,7 @@ impl Reader {
                     })
                     .collect(),
             };
-            res[i] = Some(s);
+            res[i] = Some(s)
         }
 
         res[0].take().unwrap()
diff --git a/crates/proc_macro_srv/src/abis/abi_1_47/rustc_server.rs b/crates/proc_macro_srv/src/abis/abi_1_47/rustc_server.rs
index a77433a551..ff4976d0cb 100644
--- a/crates/proc_macro_srv/src/abis/abi_1_47/rustc_server.rs
+++ b/crates/proc_macro_srv/src/abis/abi_1_47/rustc_server.rs
@@ -497,9 +497,10 @@ impl server::Literal for Rustc {
     }
 
     fn integer(&mut self, n: &str) -> Self::Literal {
-        let n = match n.parse::<i128>() {
-            Ok(n) => n.to_string(),
-            Err(_) => n.parse::<u128>().unwrap().to_string(),
+        let n = if let Ok(n) = n.parse::<i128>() {
+            n.to_string()
+        } else {
+            n.parse::<u128>().unwrap().to_string()
         };
         Literal { text: n.into(), id: tt::TokenId::unspecified() }
     }
diff --git a/crates/proc_macro_srv/src/abis/abi_1_55/rustc_server.rs b/crates/proc_macro_srv/src/abis/abi_1_55/rustc_server.rs
index 498fa7ea2d..56f9853399 100644
--- a/crates/proc_macro_srv/src/abis/abi_1_55/rustc_server.rs
+++ b/crates/proc_macro_srv/src/abis/abi_1_55/rustc_server.rs
@@ -500,9 +500,10 @@ impl server::Literal for Rustc {
     }
 
     fn integer(&mut self, n: &str) -> Self::Literal {
-        let n = match n.parse::<i128>() {
-            Ok(n) => n.to_string(),
-            Err(_) => n.parse::<u128>().unwrap().to_string(),
+        let n = if let Ok(n) = n.parse::<i128>() {
+            n.to_string()
+        } else {
+            n.parse::<u128>().unwrap().to_string()
         };
         Literal { text: n.into(), id: tt::TokenId::unspecified() }
     }
diff --git a/crates/proc_macro_srv/src/abis/abi_1_56/rustc_server.rs b/crates/proc_macro_srv/src/abis/abi_1_56/rustc_server.rs
index fb8a4c8cbe..c1064ae968 100644
--- a/crates/proc_macro_srv/src/abis/abi_1_56/rustc_server.rs
+++ b/crates/proc_macro_srv/src/abis/abi_1_56/rustc_server.rs
@@ -504,9 +504,10 @@ impl server::Literal for Rustc {
     }
 
     fn integer(&mut self, n: &str) -> Self::Literal {
-        let n = match n.parse::<i128>() {
-            Ok(n) => n.to_string(),
-            Err(_) => n.parse::<u128>().unwrap().to_string(),
+        let n = if let Ok(n) = n.parse::<i128>() {
+            n.to_string()
+        } else {
+            n.parse::<u128>().unwrap().to_string()
         };
         Literal { text: n.into(), id: tt::TokenId::unspecified() }
     }
diff --git a/crates/profile/src/hprof.rs b/crates/profile/src/hprof.rs
index b562c193e7..3ce11fd86c 100644
--- a/crates/profile/src/hprof.rs
+++ b/crates/profile/src/hprof.rs
@@ -93,7 +93,7 @@ struct ProfilerImpl {
 impl ProfileSpan {
     pub fn detail(mut self, detail: impl FnOnce() -> String) -> ProfileSpan {
         if let Some(profiler) = &mut self.0 {
-            profiler.detail = Some(detail());
+            profiler.detail = Some(detail())
         }
         self
     }
@@ -114,7 +114,7 @@ impl HeartbeatSpan {
     #[inline]
     pub fn new(enabled: bool) -> Self {
         if enabled {
-            with_profile_stack(|it| it.heartbeats(true));
+            with_profile_stack(|it| it.heartbeats(true))
         }
         Self { enabled }
     }
@@ -123,7 +123,7 @@ impl HeartbeatSpan {
 impl Drop for HeartbeatSpan {
     fn drop(&mut self) {
         if self.enabled {
-            with_profile_stack(|it| it.heartbeats(false));
+            with_profile_stack(|it| it.heartbeats(false))
         }
     }
 }
@@ -238,7 +238,7 @@ impl ProfileStack {
             self.heartbeat(frame.heartbeats);
             let avg_span = duration / (frame.heartbeats + 1);
             if avg_span > self.filter.heartbeat_longer_than {
-                eprintln!("Too few heartbeats {} ({}/{:?})?", label, frame.heartbeats, duration);
+                eprintln!("Too few heartbeats {} ({}/{:?})?", label, frame.heartbeats, duration)
             }
         }
 
@@ -292,7 +292,7 @@ fn print(
         accounted_for += tree[child].duration;
 
         if tree[child].duration.as_millis() > longer_than.as_millis() {
-            print(tree, child, level + 1, longer_than, out);
+            print(tree, child, level + 1, longer_than, out)
         } else {
             let (total_duration, cnt) =
                 short_children.entry(tree[child].label).or_insert((Duration::default(), 0));
@@ -301,7 +301,7 @@ fn print(
         }
     }
 
-    for (child_msg, (duration, count)) in &short_children {
+    for (child_msg, (duration, count)) in short_children.iter() {
         writeln!(out, "    {}{} - {} ({} calls)", current_indent, ms(*duration), child_msg, count)
             .expect("printing profiling info");
     }
diff --git a/crates/profile/src/lib.rs b/crates/profile/src/lib.rs
index c5f6bccca1..5ea5039dba 100644
--- a/crates/profile/src/lib.rs
+++ b/crates/profile/src/lib.rs
@@ -92,7 +92,7 @@ pub fn cpu_span() -> CpuSpan {
     {
         eprintln!(
             r#"cpu profiling is disabled, uncomment `default = [ "cpu_profiler" ]` in Cargo.toml to enable."#
-        );
+        )
     }
 
     CpuSpan { _private: () }
diff --git a/crates/profile/src/stop_watch.rs b/crates/profile/src/stop_watch.rs
index 43b1ce9e6d..112d03a9c7 100644
--- a/crates/profile/src/stop_watch.rs
+++ b/crates/profile/src/stop_watch.rs
@@ -70,15 +70,15 @@ impl fmt::Display for StopWatchSpan {
             let mut prefix = "";
             if instructions > 10000 {
                 instructions /= 1000;
-                prefix = "k";
+                prefix = "k"
             }
             if instructions > 10000 {
                 instructions /= 1000;
-                prefix = "m";
+                prefix = "m"
             }
             if instructions > 10000 {
                 instructions /= 1000;
-                prefix = "g";
+                prefix = "g"
             }
             write!(f, ", {}{}instr", instructions, prefix)?;
         }
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index a7a7cb6da0..153cbc1c84 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -188,13 +188,13 @@ fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
 }
 
 fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
-    let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
-    tracing::debug!("Checking sysroot: {}", rust_src.display());
-    if fs::metadata(&rust_src).is_ok() {
-        Some(rust_src)
-    } else {
-        None
-    }
+    // Try the new path first since the old one still exists.
+    let rust_src = sysroot_path.join("lib/rustlib/src/rust");
+    tracing::debug!(
+        "Checking sysroot (looking for `library` and `src` dirs): {}",
+        rust_src.display()
+    );
+    ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| fs::metadata(it).is_ok())
 }
 
 const SYSROOT_CRATES: &str = "
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index cb79ce08ba..714e2dd301 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -563,12 +563,11 @@ fn cargo_to_crate_graph(
                     pkg_to_lib_crate.insert(pkg, crate_id);
                 }
                 if let Some(proc_macro) = libproc_macro {
-                    add_dep_with_prelude(
+                    add_dep(
                         &mut crate_graph,
                         crate_id,
                         CrateName::new("proc_macro").unwrap(),
                         proc_macro,
-                        cargo[tgt].is_proc_macro,
                     );
                 }
 
diff --git a/crates/rust-analyzer/build.rs b/crates/rust-analyzer/build.rs
index 99780343fd..aceab82d9b 100644
--- a/crates/rust-analyzer/build.rs
+++ b/crates/rust-analyzer/build.rs
@@ -4,7 +4,7 @@ use std::{env, path::PathBuf, process::Command};
 
 fn main() {
     set_rerun();
-    println!("cargo:rustc-env=REV={}", rev());
+    println!("cargo:rustc-env=REV={}", rev())
 }
 
 fn set_rerun() {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6c09833368..a032c2b653 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -12,7 +12,7 @@ use std::{ffi::OsString, iter, path::PathBuf};
 use flycheck::FlycheckConfig;
 use ide::{
     AssistConfig, CompletionConfig, DiagnosticsConfig, HighlightRelatedConfig, HoverConfig,
-    HoverDocFormat, InlayHintsConfig, JoinLinesConfig,
+    HoverDocFormat, InlayHintsConfig, JoinLinesConfig, Snippet, SnippetScope,
 };
 use ide_db::helpers::{
     insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -112,6 +112,8 @@ config_data! {
         completion_addCallArgumentSnippets: bool = "true",
         /// Whether to add parenthesis when completing functions.
         completion_addCallParenthesis: bool      = "true",
+        /// Custom completion snippets.
+        completion_snippets: FxHashMap<String, SnippetDef> = "{}",
         /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
         completion_postfix_enable: bool          = "true",
         /// Toggles the additional completions that automatically add imports when completed.
@@ -277,9 +279,9 @@ config_data! {
         rustfmt_enableRangeFormatting: bool = "false",
 
         /// Workspace symbol search scope.
-        workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"",
+        workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
         /// Workspace symbol search kind.
-        workspace_symbol_search_kind: WorskpaceSymbolSearchKindDef = "\"only_types\"",
+        workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
     }
 }
 
@@ -296,6 +298,7 @@ pub struct Config {
     detached_files: Vec<AbsPathBuf>,
     pub discovered_projects: Option<Vec<ProjectManifest>>,
     pub root_path: AbsPathBuf,
+    snippets: Vec<Snippet>,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -431,6 +434,7 @@ impl Config {
             detached_files: Vec::new(),
             discovered_projects: None,
             root_path,
+            snippets: Default::default(),
         }
     }
     pub fn update(&mut self, mut json: serde_json::Value) {
@@ -443,6 +447,28 @@ impl Config {
             .map(AbsPathBuf::assert)
             .collect();
         self.data = ConfigData::from_json(json);
+        self.snippets.clear();
+        for (name, def) in self.data.completion_snippets.iter() {
+            if def.prefix.is_empty() && def.postfix.is_empty() {
+                continue;
+            }
+            let scope = match def.scope {
+                SnippetScopeDef::Expr => SnippetScope::Expr,
+                SnippetScopeDef::Type => SnippetScope::Type,
+                SnippetScopeDef::Item => SnippetScope::Item,
+            };
+            match Snippet::new(
+                &def.prefix,
+                &def.postfix,
+                &def.body,
+                def.description.as_ref().unwrap_or(name),
+                &def.requires,
+                scope,
+            ) {
+                Some(snippet) => self.snippets.push(snippet),
+                None => tracing::info!("Invalid snippet {}", name),
+            }
+        }
     }
 
     pub fn json_schema() -> serde_json::Value {
@@ -778,6 +804,7 @@ impl Config {
                     .snippet_support?,
                 false
             )),
+            snippets: self.snippets.clone(),
         }
     }
     pub fn assist(&self) -> AssistConfig {
@@ -848,14 +875,14 @@ impl Config {
     pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
         WorkspaceSymbolConfig {
             search_scope: match self.data.workspace_symbol_search_scope {
-                WorskpaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
-                WorskpaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
+                WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
+                WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
                     WorkspaceSymbolSearchScope::WorkspaceAndDependencies
                 }
             },
             search_kind: match self.data.workspace_symbol_search_kind {
-                WorskpaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
-                WorskpaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
+                WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
+                WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
             },
         }
     }
@@ -908,6 +935,66 @@ impl Config {
     }
 }
 
+#[derive(Deserialize, Debug, Clone, Copy)]
+#[serde(rename_all = "snake_case")]
+enum SnippetScopeDef {
+    Expr,
+    Item,
+    Type,
+}
+
+impl Default for SnippetScopeDef {
+    fn default() -> Self {
+        SnippetScopeDef::Expr
+    }
+}
+
+#[derive(Deserialize, Debug, Clone, Default)]
+#[serde(default)]
+struct SnippetDef {
+    #[serde(deserialize_with = "single_or_array")]
+    prefix: Vec<String>,
+    #[serde(deserialize_with = "single_or_array")]
+    postfix: Vec<String>,
+    description: Option<String>,
+    #[serde(deserialize_with = "single_or_array")]
+    body: Vec<String>,
+    #[serde(deserialize_with = "single_or_array")]
+    requires: Vec<String>,
+    scope: SnippetScopeDef,
+}
+
+fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    struct SingleOrVec;
+
+    impl<'de> serde::de::Visitor<'de> for SingleOrVec {
+        type Value = Vec<String>;
+
+        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+            formatter.write_str("string or array of strings")
+        }
+
+        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+        where
+            E: serde::de::Error,
+        {
+            Ok(vec![value.to_owned()])
+        }
+
+        fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
+        where
+            A: serde::de::SeqAccess<'de>,
+        {
+            Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
+        }
+    }
+
+    deserializer.deserialize_any(SingleOrVec)
+}
+
 #[derive(Deserialize, Debug, Clone)]
 #[serde(untagged)]
 enum ManifestOrProjectJson {
@@ -939,14 +1026,14 @@ enum ImportPrefixDef {
 
 #[derive(Deserialize, Debug, Clone)]
 #[serde(rename_all = "snake_case")]
-enum WorskpaceSymbolSearchScopeDef {
+enum WorkspaceSymbolSearchScopeDef {
     Workspace,
     WorkspaceAndDependencies,
 }
 
 #[derive(Deserialize, Debug, Clone)]
 #[serde(rename_all = "snake_case")]
-enum WorskpaceSymbolSearchKindDef {
+enum WorkspaceSymbolSearchKindDef {
     OnlyTypes,
     AllSymbols,
 }
@@ -1077,6 +1164,9 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
             "items": { "type": "string" },
             "uniqueItems": true,
         },
+        "FxHashMap<String, SnippetDef>" => set! {
+            "type": "object",
+        },
         "FxHashMap<String, String>" => set! {
             "type": "object",
         },
@@ -1133,7 +1223,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
             "type": "array",
             "items": { "type": ["string", "object"] },
         },
-        "WorskpaceSymbolSearchScopeDef" => set! {
+        "WorkspaceSymbolSearchScopeDef" => set! {
             "type": "string",
             "enum": ["workspace", "workspace_and_dependencies"],
             "enumDescriptions": [
@@ -1141,7 +1231,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 "Search in current workspace and dependencies"
             ],
         },
-        "WorskpaceSymbolSearchKindDef" => set! {
+        "WorkspaceSymbolSearchKindDef" => set! {
             "type": "string",
             "enum": ["only_types", "all_symbols"],
             "enumDescriptions": [
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 6cb6b0a8d0..6ad5cb5334 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -427,9 +427,10 @@ pub(crate) fn handle_workspace_symbol(
         // If no explicit marker was set, check request params. If that's also empty
         // use global config.
         if !all_symbols {
-            let search_kind = match params.search_kind {
-                Some(ref search_kind) => search_kind,
-                None => &config.search_kind,
+            let search_kind = if let Some(ref search_kind) = params.search_kind {
+                search_kind
+            } else {
+                &config.search_kind
             };
             all_symbols = match search_kind {
                 lsp_ext::WorkspaceSymbolSearchKind::OnlyTypes => false,
@@ -438,9 +439,10 @@ pub(crate) fn handle_workspace_symbol(
         }
 
         if !libs {
-            let search_scope = match params.search_scope {
-                Some(ref search_scope) => search_scope,
-                None => &config.search_scope,
+            let search_scope = if let Some(ref search_scope) = params.search_scope {
+                search_scope
+            } else {
+                &config.search_scope
             };
             libs = match search_scope {
                 lsp_ext::WorkspaceSymbolSearchScope::Workspace => false,
@@ -785,8 +787,10 @@ pub(crate) fn handle_completion_resolve(
         .resolve_completion_edits(
             &snap.config.completion(),
             FilePosition { file_id, offset },
-            &resolve_data.full_import_path,
-            resolve_data.imported_name,
+            resolve_data
+                .imports
+                .into_iter()
+                .map(|import| (import.full_import_path, import.imported_name)),
         )?
         .into_iter()
         .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 036cfe157b..b10eb3d6e9 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -144,6 +144,7 @@ fn integrated_completion_benchmark() {
                 group: true,
                 skip_glob_imports: true,
             },
+            snippets: Vec::new(),
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -180,6 +181,7 @@ fn integrated_completion_benchmark() {
                 group: true,
                 skip_glob_imports: true,
             },
+            snippets: Vec::new(),
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 521691d5ec..19137b942e 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -520,6 +520,11 @@ pub enum WorkspaceSymbolSearchKind {
 #[derive(Debug, Serialize, Deserialize)]
 pub struct CompletionResolveData {
     pub position: lsp_types::TextDocumentPositionParams,
+    pub imports: Vec<CompletionImport>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CompletionImport {
     pub full_import_path: String,
     pub imported_name: String,
 }
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 59a768397f..fc3e25064f 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -270,14 +270,20 @@ fn completion_item(
         lsp_item.insert_text_format = Some(lsp_types::InsertTextFormat::Snippet);
     }
     if config.completion().enable_imports_on_the_fly {
-        if let Some(import_edit) = item.import_to_add() {
-            let import_path = &import_edit.import.import_path;
-            if let Some(import_name) = import_path.segments().last() {
-                let data = lsp_ext::CompletionResolveData {
-                    position: tdpp.clone(),
-                    full_import_path: import_path.to_string(),
-                    imported_name: import_name.to_string(),
-                };
+        if let imports @ [_, ..] = item.imports_to_add() {
+            let imports: Vec<_> = imports
+                .iter()
+                .filter_map(|import_edit| {
+                    let import_path = &import_edit.import.import_path;
+                    let import_name = import_path.segments().last()?;
+                    Some(lsp_ext::CompletionImport {
+                        full_import_path: import_path.to_string(),
+                        imported_name: import_name.to_string(),
+                    })
+                })
+                .collect();
+            if !imports.is_empty() {
+                let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports };
                 lsp_item.data = Some(to_value(data).unwrap());
             }
         }
diff --git a/crates/sourcegen/src/lib.rs b/crates/sourcegen/src/lib.rs
index 398e846a14..6a332bce85 100644
--- a/crates/sourcegen/src/lib.rs
+++ b/crates/sourcegen/src/lib.rs
@@ -33,9 +33,9 @@ pub fn list_files(dir: &Path) -> Vec<PathBuf> {
                 path.file_name().unwrap_or_default().to_str().unwrap_or_default().starts_with('.');
             if !is_hidden {
                 if file_type.is_dir() {
-                    work.push(path);
+                    work.push(path)
                 } else if file_type.is_file() {
-                    res.push(path);
+                    res.push(path)
                 }
             }
         }
@@ -66,7 +66,7 @@ impl CommentBlock {
                         panic!(
                             "Use plain (non-doc) comments with tags like {}:\n    {}",
                             tag, first
-                        );
+                        )
                     }
 
                     block.id = id.trim().to_string();
@@ -106,7 +106,7 @@ impl CommentBlock {
             }
         }
         if !block.contents.is_empty() {
-            res.push(block);
+            res.push(block)
         }
         res
     }
@@ -139,7 +139,7 @@ fn ensure_rustfmt() {
         panic!(
             "Failed to run rustfmt from toolchain 'stable'. \
                  Please run `rustup component add rustfmt --toolchain stable` to install it.",
-        );
+        )
     }
 }
 
@@ -185,7 +185,7 @@ pub fn ensure_file_contents(file: &Path, contents: &str) {
         let _ = fs::create_dir_all(parent);
     }
     fs::write(file, contents).unwrap();
-    panic!("some file was not up to date and has been updated, simply re-run the tests");
+    panic!("some file was not up to date and has been updated, simply re-run the tests")
 }
 
 fn normalize_newlines(s: &str) -> String {
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index bfa6024679..e7d4753de0 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -45,7 +45,7 @@ fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
         if c.is_ascii_uppercase() && prev {
             // This check is required to not translate `Weird_Case` into `weird__case`.
             if !buf.ends_with('_') {
-                buf.push('_');
+                buf.push('_')
             }
         }
         prev = true;
@@ -60,7 +60,7 @@ pub fn replace(buf: &mut String, from: char, to: &str) {
         return;
     }
     // FIXME: do this in place.
-    *buf = buf.replace(from, to);
+    *buf = buf.replace(from, to)
 }
 
 pub fn trim_indent(mut text: &str) -> String {
@@ -101,7 +101,7 @@ pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
     impl<F: FnOnce()> Drop for D<F> {
         fn drop(&mut self) {
             if let Some(f) = self.0.take() {
-                f();
+                f()
             }
         }
     }
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
index f8fafc5a67..26debf3c47 100644
--- a/crates/stdx/src/panic_context.rs
+++ b/crates/stdx/src/panic_context.rs
@@ -25,19 +25,19 @@ impl PanicContext {
                 if !ctx.is_empty() {
                     eprintln!("Panic context:");
                     for frame in ctx.iter() {
-                        eprintln!("> {}\n", frame);
+                        eprintln!("> {}\n", frame)
                     }
                 }
-                default_hook(panic_info);
-            });
+                default_hook(panic_info)
+            })
         };
-        panic::set_hook(Box::new(hook));
+        panic::set_hook(Box::new(hook))
     }
 }
 
 impl Drop for PanicContext {
     fn drop(&mut self) {
-        with_ctx(|ctx| assert!(ctx.pop().is_some()));
+        with_ctx(|ctx| assert!(ctx.pop().is_some()))
     }
 }
 
@@ -45,5 +45,5 @@ fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
     thread_local! {
         static CTX: RefCell<Vec<String>> = RefCell::new(Vec::new());
     }
-    CTX.with(|ctx| f(&mut *ctx.borrow_mut()));
+    CTX.with(|ctx| f(&mut *ctx.borrow_mut()))
 }
diff --git a/crates/stdx/src/process.rs b/crates/stdx/src/process.rs
index 34cabf7807..b290ba2f05 100644
--- a/crates/stdx/src/process.rs
+++ b/crates/stdx/src/process.rs
@@ -42,9 +42,9 @@ pub fn streaming_output(
                 };
                 for line in String::from_utf8_lossy(new_lines).lines() {
                     if is_out {
-                        on_stdout_line(line);
+                        on_stdout_line(line)
                     } else {
-                        on_stderr_line(line);
+                        on_stderr_line(line)
                     }
                 }
             }
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index bce02971c6..9c7f93e130 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -112,15 +112,15 @@ impl TreeDiff {
     pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
         let _p = profile::span("into_text_edit");
 
-        for (anchor, to) in &self.insertions {
+        for (anchor, to) in self.insertions.iter() {
             let offset = match anchor {
                 TreeDiffInsertPos::After(it) => it.text_range().end(),
                 TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(),
             };
             to.iter().for_each(|to| builder.insert(offset, to.to_string()));
         }
-        for (from, to) in &self.replacements {
-            builder.replace(from.text_range(), to.to_string());
+        for (from, to) in self.replacements.iter() {
+            builder.replace(from.text_range(), to.to_string())
         }
         for text_range in self.deletions.iter().map(SyntaxElement::text_range) {
             builder.delete(text_range);
@@ -217,8 +217,9 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
                             cov_mark::hit!(diff_insertions);
                             insert = true;
                             break;
+                        } else {
+                            look_ahead_scratch.push(rhs_child);
                         }
-                        look_ahead_scratch.push(rhs_child);
                     }
                     let drain = look_ahead_scratch.drain(..);
                     if insert {
@@ -232,7 +233,7 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
                         diff.insertions.entry(insert_pos).or_insert_with(Vec::new).extend(drain);
                         rhs_children = rhs_children_clone;
                     } else {
-                        go(diff, lhs_ele, rhs_ele);
+                        go(diff, lhs_ele, rhs_ele)
                     }
                 }
             }
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 43a9c6756d..15e99ff0e9 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -126,7 +126,7 @@ impl IndentLevel {
             if let Some(ws) = ast::Whitespace::cast(token) {
                 if ws.text().contains('\n') {
                     let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self));
-                    ted::replace(ws.syntax(), &new_ws);
+                    ted::replace(ws.syntax(), &new_ws)
                 }
             }
         }
@@ -143,7 +143,7 @@ impl IndentLevel {
                     let new_ws = make::tokens::whitespace(
                         &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
                     );
-                    ted::replace(ws.syntax(), &new_ws);
+                    ted::replace(ws.syntax(), &new_ws)
                 }
             }
         }
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 552d6fc1e3..d271b5f836 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -49,7 +49,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
             } else {
                 Position::last_child_of(self.syntax())
             };
-            create_where_clause(position);
+            create_where_clause(position)
         }
         self.where_clause().unwrap()
     }
@@ -60,9 +60,10 @@ impl GenericParamsOwnerEdit for ast::Impl {
         match self.generic_param_list() {
             Some(it) => it,
             None => {
-                let position = match self.impl_token() {
-                    Some(imp_token) => Position::after(imp_token),
-                    None => Position::last_child_of(self.syntax()),
+                let position = if let Some(imp_token) = self.impl_token() {
+                    Position::after(imp_token)
+                } else {
+                    Position::last_child_of(self.syntax())
                 };
                 create_generic_param_list(position)
             }
@@ -71,11 +72,12 @@ impl GenericParamsOwnerEdit for ast::Impl {
 
     fn get_or_create_where_clause(&self) -> ast::WhereClause {
         if self.where_clause().is_none() {
-            let position = match self.assoc_item_list() {
-                Some(items) => Position::before(items.syntax()),
-                None => Position::last_child_of(self.syntax()),
+            let position = if let Some(items) = self.assoc_item_list() {
+                Position::before(items.syntax())
+            } else {
+                Position::last_child_of(self.syntax())
             };
-            create_where_clause(position);
+            create_where_clause(position)
         }
         self.where_clause().unwrap()
     }
@@ -100,11 +102,12 @@ impl GenericParamsOwnerEdit for ast::Trait {
 
     fn get_or_create_where_clause(&self) -> ast::WhereClause {
         if self.where_clause().is_none() {
-            let position = match self.assoc_item_list() {
-                Some(items) => Position::before(items.syntax()),
-                None => Position::last_child_of(self.syntax()),
+            let position = if let Some(items) = self.assoc_item_list() {
+                Position::before(items.syntax())
+            } else {
+                Position::last_child_of(self.syntax())
             };
-            create_where_clause(position);
+            create_where_clause(position)
         }
         self.where_clause().unwrap()
     }
@@ -142,7 +145,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
             } else {
                 Position::last_child_of(self.syntax())
             };
-            create_where_clause(position);
+            create_where_clause(position)
         }
         self.where_clause().unwrap()
     }
@@ -174,7 +177,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
             } else {
                 Position::last_child_of(self.syntax())
             };
-            create_where_clause(position);
+            create_where_clause(position)
         }
         self.where_clause().unwrap()
     }
@@ -231,7 +234,7 @@ impl ast::GenericParamList {
             }
             None => {
                 let after_l_angle = Position::after(self.l_angle_token().unwrap());
-                ted::insert(after_l_angle, generic_param.syntax());
+                ted::insert(after_l_angle, generic_param.syntax())
             }
         }
     }
@@ -244,15 +247,18 @@ impl ast::WhereClause {
                 ted::append_child_raw(self.syntax(), make::token(T![,]));
             }
         }
-        ted::append_child(self.syntax(), predicate.syntax());
+        ted::append_child(self.syntax(), predicate.syntax())
     }
 }
 
 impl ast::TypeBoundList {
     pub fn remove(&self) {
-        match self.syntax().siblings_with_tokens(Direction::Prev).find(|it| it.kind() == T![:]) {
-            Some(colon) => ted::remove_all(colon..=self.syntax().clone().into()),
-            None => ted::remove(self.syntax()),
+        if let Some(colon) =
+            self.syntax().siblings_with_tokens(Direction::Prev).find(|it| it.kind() == T![:])
+        {
+            ted::remove_all(colon..=self.syntax().clone().into())
+        } else {
+            ted::remove(self.syntax())
         }
     }
 }
@@ -261,7 +267,7 @@ impl ast::PathSegment {
     pub fn get_or_create_generic_arg_list(&self) -> ast::GenericArgList {
         if self.generic_arg_list().is_none() {
             let arg_list = make::generic_arg_list().clone_for_update();
-            ted::append_child(self.syntax(), arg_list.syntax());
+            ted::append_child(self.syntax(), arg_list.syntax())
         }
         self.generic_arg_list().unwrap()
     }
@@ -269,7 +275,7 @@ impl ast::PathSegment {
 
 impl ast::UseTree {
     pub fn remove(&self) {
-        for dir in [Direction::Next, Direction::Prev] {
+        for &dir in [Direction::Next, Direction::Prev].iter() {
             if let Some(next_use_tree) = neighbor(self, dir) {
                 let separators = self
                     .syntax()
@@ -280,7 +286,7 @@ impl ast::UseTree {
                 break;
             }
         }
-        ted::remove(self.syntax());
+        ted::remove(self.syntax())
     }
 }
 
@@ -295,13 +301,13 @@ impl ast::Use {
             let ws_text = next_ws.syntax().text();
             if let Some(rest) = ws_text.strip_prefix('\n') {
                 if rest.is_empty() {
-                    ted::remove(next_ws.syntax());
+                    ted::remove(next_ws.syntax())
                 } else {
-                    ted::replace(next_ws.syntax(), make::tokens::whitespace(rest));
+                    ted::replace(next_ws.syntax(), make::tokens::whitespace(rest))
                 }
             }
         }
-        ted::remove(self.syntax());
+        ted::remove(self.syntax())
     }
 }
 
@@ -449,7 +455,7 @@ impl ast::RecordExprField {
     /// This will either replace the initializer, or in the case that this is a shorthand convert
     /// the initializer into the name ref and insert the expr as the new initializer.
     pub fn replace_expr(&self, expr: ast::Expr) {
-        if self.name_ref().is_some() {
+        if let Some(_) = self.name_ref() {
             match self.expr() {
                 Some(prev) => ted::replace(prev.syntax(), expr.syntax()),
                 None => ted::append_child(self.syntax(), expr.syntax()),
@@ -519,7 +525,7 @@ pub trait Indent: AstNode + Clone + Sized {
     fn reindent_to(&self, target_level: IndentLevel) {
         let current_level = IndentLevel::from_node(self.syntax());
         self.dedent(current_level);
-        self.indent(target_level);
+        self.indent(target_level)
     }
 }
 
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index f16dcd56e9..40c0cd7a37 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -257,7 +257,7 @@ pub fn block_expr(
         format_to!(buf, "    {}\n", stmt);
     }
     if let Some(tail_expr) = tail_expr {
-        format_to!(buf, "    {}\n", tail_expr);
+        format_to!(buf, "    {}\n", tail_expr)
     }
     buf += "}";
     ast_from_text(&format!("fn f() {}", buf))
@@ -644,14 +644,9 @@ pub fn fn_(
     ret_type: Option<ast::RetType>,
     is_async: bool,
 ) -> ast::Fn {
-    let type_params = match type_params {
-        Some(type_params) => format!("<{}>", type_params),
-        None => "".into(),
-    };
-    let ret_type = match ret_type {
-        Some(ret_type) => format!("{} ", ret_type),
-        None => "".into(),
-    };
+    let type_params =
+        if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
+    let ret_type = if let Some(ret_type) = ret_type { format!("{} ", ret_type) } else { "".into() };
     let visibility = match visibility {
         None => String::new(),
         Some(it) => format!("{} ", it),
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 2e6544f090..800befb04b 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -276,9 +276,9 @@ impl ast::Path {
 
 impl ast::Use {
     pub fn is_simple_glob(&self) -> bool {
-        self.use_tree().map_or(false, |use_tree| {
-            use_tree.use_tree_list().is_none() && use_tree.star_token().is_some()
-        })
+        self.use_tree()
+            .map(|use_tree| use_tree.use_tree_list().is_none() && use_tree.star_token().is_some())
+            .unwrap_or(false)
     }
 }
 
@@ -549,9 +549,10 @@ impl ast::FieldExpr {
     }
 
     pub fn field_access(&self) -> Option<FieldKind> {
-        match self.name_ref() {
-            Some(nr) => Some(FieldKind::Name(nr)),
-            None => self.index_token().map(FieldKind::Index),
+        if let Some(nr) = self.name_ref() {
+            Some(FieldKind::Name(nr))
+        } else {
+            self.index_token().map(FieldKind::Index)
         }
     }
 }
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 2f8312d5d1..ad52d9f540 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -283,9 +283,10 @@ pub trait HasFormatSpecifier: AstToken {
     where
         F: FnMut(TextRange, FormatSpecifier),
     {
-        let char_ranges = match self.char_ranges() {
-            Some(char_ranges) => char_ranges,
-            None => return,
+        let char_ranges = if let Some(char_ranges) = self.char_ranges() {
+            char_ranges
+        } else {
+            return;
         };
         let mut chars = char_ranges.iter().peekable();
 
@@ -527,11 +528,10 @@ pub trait HasFormatSpecifier: AstToken {
                         }
                     }
 
-                    match chars.peek() {
-                        Some((_, Ok('}'))) => {
-                            skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
-                        }
-                        Some((_, _)) | None => continue,
+                    if let Some((_, Ok('}'))) = chars.peek() {
+                        skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
+                    } else {
+                        continue;
                     }
                 }
                 _ => {
@@ -609,7 +609,7 @@ impl HasFormatSpecifier for ast::String {
                 TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap())
                     + offset,
                 unescaped_char,
-            ));
+            ))
         });
 
         Some(res)
@@ -631,7 +631,7 @@ impl ast::IntNumber {
 
         let mut text = token.text();
         if let Some(suffix) = self.suffix() {
-            text = &text[..text.len() - suffix.len()];
+            text = &text[..text.len() - suffix.len()]
         }
 
         let radix = self.radix();
@@ -688,7 +688,7 @@ impl Radix {
     pub const ALL: &'static [Radix] =
         &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal];
 
-    const fn prefix_len(self) -> usize {
+    const fn prefix_len(&self) -> usize {
         match self {
             Self::Decimal => 0,
             _ => 2,
diff --git a/crates/syntax/src/display.rs b/crates/syntax/src/display.rs
index 95e37944cc..1498385fef 100644
--- a/crates/syntax/src/display.rs
+++ b/crates/syntax/src/display.rs
@@ -28,7 +28,7 @@ pub fn function_declaration(node: &ast::Fn) -> String {
         format_to!(buf, "{} ", abi);
     }
     if let Some(name) = node.name() {
-        format_to!(buf, "fn {}", name);
+        format_to!(buf, "fn {}", name)
     }
     if let Some(type_params) = node.generic_param_list() {
         format_to!(buf, "{}", type_params);
diff --git a/crates/syntax/src/parsing/text_token_source.rs b/crates/syntax/src/parsing/text_token_source.rs
index 11dfc63a65..0614194a56 100644
--- a/crates/syntax/src/parsing/text_token_source.rs
+++ b/crates/syntax/src/parsing/text_token_source.rs
@@ -44,7 +44,8 @@ impl<'t> TokenSource for TextTokenSource<'t> {
     fn is_keyword(&self, kw: &str) -> bool {
         self.token_offset_pairs
             .get(self.curr.1)
-            .map_or(false, |(token, offset)| &self.text[TextRange::at(*offset, token.len)] == kw)
+            .map(|(token, offset)| &self.text[TextRange::at(*offset, token.len)] == kw)
+            .unwrap_or(false)
     }
 }
 
@@ -54,7 +55,8 @@ fn mk_token(pos: usize, token_offset_pairs: &[(Token, TextSize)]) -> parser::Tok
             token.kind,
             token_offset_pairs
                 .get(pos + 1)
-                .map_or(false, |(_, next_offset)| offset + token.len == *next_offset),
+                .map(|(_, next_offset)| offset + token.len == *next_offset)
+                .unwrap_or(false),
         ),
         None => (EOF, false),
     };
diff --git a/crates/syntax/src/parsing/text_tree_sink.rs b/crates/syntax/src/parsing/text_tree_sink.rs
index 8c1de92048..25bfd41a16 100644
--- a/crates/syntax/src/parsing/text_tree_sink.rs
+++ b/crates/syntax/src/parsing/text_tree_sink.rs
@@ -88,7 +88,7 @@ impl<'a> TreeSink for TextTreeSink<'a> {
     }
 
     fn error(&mut self, error: ParseError) {
-        self.inner.error(error, self.text_pos);
+        self.inner.error(error, self.text_pos)
     }
 }
 
@@ -108,7 +108,7 @@ impl<'a> TextTreeSink<'a> {
         match mem::replace(&mut self.state, State::Normal) {
             State::PendingFinish => {
                 self.eat_trivias();
-                self.inner.finish_node();
+                self.inner.finish_node()
             }
             State::PendingStart | State::Normal => unreachable!(),
         }
diff --git a/crates/syntax/src/ptr.rs b/crates/syntax/src/ptr.rs
index 57ae64cafe..282470bae4 100644
--- a/crates/syntax/src/ptr.rs
+++ b/crates/syntax/src/ptr.rs
@@ -81,7 +81,7 @@ impl<N: AstNode> PartialEq for AstPtr<N> {
 
 impl<N: AstNode> Hash for AstPtr<N> {
     fn hash<H: Hasher>(&self, state: &mut H) {
-        self.raw.hash(state);
+        self.raw.hash(state)
     }
 }
 
diff --git a/crates/syntax/src/syntax_node.rs b/crates/syntax/src/syntax_node.rs
index bd7ea0240b..0ddfd439dc 100644
--- a/crates/syntax/src/syntax_node.rs
+++ b/crates/syntax/src/syntax_node.rs
@@ -56,19 +56,19 @@ impl SyntaxTreeBuilder {
 
     pub fn token(&mut self, kind: SyntaxKind, text: &str) {
         let kind = RustLanguage::kind_to_raw(kind);
-        self.inner.token(kind, text);
+        self.inner.token(kind, text)
     }
 
     pub fn start_node(&mut self, kind: SyntaxKind) {
         let kind = RustLanguage::kind_to_raw(kind);
-        self.inner.start_node(kind);
+        self.inner.start_node(kind)
     }
 
     pub fn finish_node(&mut self) {
-        self.inner.finish_node();
+        self.inner.finish_node()
     }
 
     pub fn error(&mut self, error: parser::ParseError, text_pos: TextSize) {
-        self.errors.push(SyntaxError::new_at_offset(*error.0, text_pos));
+        self.errors.push(SyntaxError::new_at_offset(*error.0, text_pos))
     }
 }
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index a47b4b11c0..4b0d8bf391 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -77,23 +77,23 @@ impl Position {
 }
 
 pub fn insert(position: Position, elem: impl Element) {
-    insert_all(position, vec![elem.syntax_element()]);
+    insert_all(position, vec![elem.syntax_element()])
 }
 pub fn insert_raw(position: Position, elem: impl Element) {
-    insert_all_raw(position, vec![elem.syntax_element()]);
+    insert_all_raw(position, vec![elem.syntax_element()])
 }
 pub fn insert_all(position: Position, mut elements: Vec<SyntaxElement>) {
     if let Some(first) = elements.first() {
         if let Some(ws) = ws_before(&position, first) {
-            elements.insert(0, ws.into());
+            elements.insert(0, ws.into())
         }
     }
     if let Some(last) = elements.last() {
         if let Some(ws) = ws_after(&position, last) {
-            elements.push(ws.into());
+            elements.push(ws.into())
         }
     }
-    insert_all_raw(position, elements);
+    insert_all_raw(position, elements)
 }
 pub fn insert_all_raw(position: Position, elements: Vec<SyntaxElement>) {
     let (parent, index) = match position.repr {
@@ -104,10 +104,10 @@ pub fn insert_all_raw(position: Position, elements: Vec<SyntaxElement>) {
 }
 
 pub fn remove(elem: impl Element) {
-    elem.syntax_element().detach();
+    elem.syntax_element().detach()
 }
 pub fn remove_all(range: RangeInclusive<SyntaxElement>) {
-    replace_all(range, Vec::new());
+    replace_all(range, Vec::new())
 }
 pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
     let mut it = range.into_iter();
@@ -115,9 +115,9 @@ pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
         match it.last() {
             Some(mut last) => {
                 if first.index() > last.index() {
-                    mem::swap(&mut first, &mut last);
+                    mem::swap(&mut first, &mut last)
                 }
-                remove_all(first..=last);
+                remove_all(first..=last)
             }
             None => remove(first),
         }
@@ -125,26 +125,26 @@ pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
 }
 
 pub fn replace(old: impl Element, new: impl Element) {
-    replace_with_many(old, vec![new.syntax_element()]);
+    replace_with_many(old, vec![new.syntax_element()])
 }
 pub fn replace_with_many(old: impl Element, new: Vec<SyntaxElement>) {
     let old = old.syntax_element();
-    replace_all(old.clone()..=old, new);
+    replace_all(old.clone()..=old, new)
 }
 pub fn replace_all(range: RangeInclusive<SyntaxElement>, new: Vec<SyntaxElement>) {
     let start = range.start().index();
     let end = range.end().index();
     let parent = range.start().parent().unwrap();
-    parent.splice_children(start..end + 1, new);
+    parent.splice_children(start..end + 1, new)
 }
 
 pub fn append_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
     let position = Position::last_child_of(node);
-    insert(position, child);
+    insert(position, child)
 }
 pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
     let position = Position::last_child_of(node);
-    insert_raw(position, child);
+    insert_raw(position, child)
 }
 
 fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index 521ffce20e..b2d42f6651 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -137,7 +137,7 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
                         if let Err(err) = char {
                             push_err(1, (range.start, err));
                         }
-                    });
+                    })
                 }
             }
         }
@@ -148,7 +148,7 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
                         if let Err(err) = char {
                             push_err(2, (range.start, err));
                         }
-                    });
+                    })
                 }
             }
         }
diff --git a/crates/syntax/src/validation/block.rs b/crates/syntax/src/validation/block.rs
index 8eb4a10a3f..9076b82503 100644
--- a/crates/syntax/src/validation/block.rs
+++ b/crates/syntax/src/validation/block.rs
@@ -19,6 +19,6 @@ pub(crate) fn validate_block_expr(block: ast::BlockExpr, errors: &mut Vec<Syntax
                 "A block in this position cannot accept inner attributes",
                 attr.syntax().text_range(),
             )
-        }));
+        }))
     }
 }
diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs
index e9700c560a..a43ffe202f 100644
--- a/crates/text_edit/src/lib.rs
+++ b/crates/text_edit/src/lib.rs
@@ -90,13 +90,13 @@ impl TextEdit {
         }
 
         let mut total_len = TextSize::of(&*text);
-        for indel in &self.indels {
+        for indel in self.indels.iter() {
             total_len += TextSize::of(&indel.insert);
             total_len -= indel.delete.end() - indel.delete.start();
         }
         let mut buf = String::with_capacity(total_len.into());
         let mut prev = 0;
-        for indel in &self.indels {
+        for indel in self.indels.iter() {
             let start: usize = indel.delete.start().into();
             let end: usize = indel.delete.end().into();
             if start > prev {
@@ -110,7 +110,7 @@ impl TextEdit {
 
         // FIXME: figure out a way to mutate the text in-place or reuse the
         // memory in some other way
-        *text = buf;
+        *text = buf
     }
 
     pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> {
@@ -126,7 +126,7 @@ impl TextEdit {
 
     pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
         let mut res = offset;
-        for indel in &self.indels {
+        for indel in self.indels.iter() {
             if indel.delete.start() >= offset {
                 break;
             }
@@ -163,13 +163,13 @@ impl TextEditBuilder {
         self.indels.is_empty()
     }
     pub fn replace(&mut self, range: TextRange, replace_with: String) {
-        self.indel(Indel::replace(range, replace_with));
+        self.indel(Indel::replace(range, replace_with))
     }
     pub fn delete(&mut self, range: TextRange) {
-        self.indel(Indel::delete(range));
+        self.indel(Indel::delete(range))
     }
     pub fn insert(&mut self, offset: TextSize, text: String) {
-        self.indel(Indel::insert(offset, text));
+        self.indel(Indel::insert(offset, text))
     }
     pub fn finish(self) -> TextEdit {
         let mut indels = self.indels;
diff --git a/crates/tt/src/buffer.rs b/crates/tt/src/buffer.rs
index 1fa61c41dd..dc577700fd 100644
--- a/crates/tt/src/buffer.rs
+++ b/crates/tt/src/buffer.rs
@@ -194,7 +194,8 @@ impl<'a> Cursor<'a> {
                 TokenTree::Subtree(subtree) => Some(TokenTreeRef::Subtree(subtree, Some(tt))),
             },
             Some(Entry::Subtree(tt, subtree, _)) => Some(TokenTreeRef::Subtree(subtree, *tt)),
-            Some(Entry::End(_)) | None => None,
+            Some(Entry::End(_)) => None,
+            None => None,
         }
     }
 
@@ -205,9 +206,10 @@ impl<'a> Cursor<'a> {
     /// Bump the cursor
     pub fn bump(self) -> Cursor<'a> {
         if let Some(Entry::End(exit)) = self.buffer.entry(&self.ptr) {
-            match exit {
-                Some(exit) => Cursor::create(self.buffer, *exit),
-                None => self,
+            if let Some(exit) = exit {
+                Cursor::create(self.buffer, *exit)
+            } else {
+                self
             }
         } else {
             Cursor::create(self.buffer, EntryPtr(self.ptr.0, self.ptr.1 + 1))
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 9eca970ee2..66180bb156 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -161,7 +161,7 @@ impl fmt::Display for Subtree {
         };
         f.write_str(l)?;
         let mut needs_space = false;
-        for tt in &self.token_trees {
+        for tt in self.token_trees.iter() {
             if needs_space {
                 f.write_str(" ")?;
             }
@@ -169,7 +169,7 @@ impl fmt::Display for Subtree {
             match tt {
                 TokenTree::Leaf(Leaf::Punct(p)) => {
                     needs_space = p.spacing == Spacing::Alone;
-                    fmt::Display::fmt(p, f)?;
+                    fmt::Display::fmt(p, f)?
                 }
                 tt => fmt::Display::fmt(tt, f)?,
             }
@@ -215,7 +215,7 @@ impl Subtree {
             .iter()
             .map(|c| match c {
                 TokenTree::Subtree(c) => c.count(),
-                TokenTree::Leaf(_) => 0,
+                _ => 0,
             })
             .sum::<usize>();
 
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index a2a53cdc9e..4fb34aed07 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -38,7 +38,7 @@ impl loader::Handle for NotifyHandle {
         NotifyHandle { sender, _thread: thread }
     }
     fn set_config(&mut self, config: loader::Config) {
-        self.sender.send(Message::Config(config)).unwrap();
+        self.sender.send(Message::Config(config)).unwrap()
     }
     fn invalidate(&mut self, path: AbsPathBuf) {
         self.sender.send(Message::Invalidate(path)).unwrap();
@@ -84,7 +84,7 @@ impl NotifyActor {
                         if !config.watch.is_empty() {
                             let (watcher_sender, watcher_receiver) = unbounded();
                             let watcher = log_notify_error(RecommendedWatcher::new(move |event| {
-                                watcher_sender.send(event).unwrap();
+                                watcher_sender.send(event).unwrap()
                             }));
                             self.watcher = watcher.map(|it| (it, watcher_receiver));
                         }
@@ -99,7 +99,7 @@ impl NotifyActor {
                         for (i, entry) in config.load.into_iter().enumerate() {
                             let watch = config.watch.contains(&i);
                             if watch {
-                                self.watched_entries.push(entry.clone());
+                                self.watched_entries.push(entry.clone())
                             }
                             let files = self.load_entry(entry, watch);
                             self.send(loader::Message::Loaded { files });
@@ -149,7 +149,7 @@ impl NotifyActor {
                                 Some((path, contents))
                             })
                             .collect();
-                        self.send(loader::Message::Loaded { files });
+                        self.send(loader::Message::Loaded { files })
                     }
                 }
             }
@@ -165,7 +165,7 @@ impl NotifyActor {
                 .into_iter()
                 .map(|file| {
                     if watch {
-                        self.watch(file.clone());
+                        self.watch(file.clone())
                     }
                     let contents = read(file.as_path());
                     (file, contents)
@@ -174,7 +174,7 @@ impl NotifyActor {
             loader::Entry::Directories(dirs) => {
                 let mut res = Vec::new();
 
-                for root in &dirs.include {
+                for root in dirs.include.iter() {
                     let walkdir =
                         WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
                             if !entry.file_type().is_dir() {
@@ -218,7 +218,7 @@ impl NotifyActor {
         }
     }
     fn send(&mut self, msg: loader::Message) {
-        (self.sender)(msg);
+        (self.sender)(msg)
     }
 }
 
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 6a89263e53..0011f73c96 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -112,7 +112,7 @@ impl FileSetConfig {
         let mut res = vec![FileSet::default(); self.len()];
         for (file_id, path) in vfs.iter() {
             let root = self.classify(path, &mut scratch_space);
-            res[root].insert(file_id, path.clone());
+            res[root].insert(file_id, path.clone())
         }
         res
     }
@@ -157,7 +157,7 @@ impl FileSetConfigBuilder {
 
     /// Add a new set of paths prefixes.
     pub fn add_file_set(&mut self, roots: Vec<VfsPath>) {
-        self.roots.push(roots);
+        self.roots.push(roots)
     }
 
     /// Build the `FileSetConfig`.
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index 39a1666421..ffd673573a 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -73,8 +73,9 @@ impl VfsPath {
     pub fn starts_with(&self, other: &VfsPath) -> bool {
         match (&self.0, &other.0) {
             (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
+            (VfsPathRepr::PathBuf(_), _) => false,
             (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
-            (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => false,
+            (VfsPathRepr::VirtualPath(_), _) => false,
         }
     }
 
@@ -356,7 +357,7 @@ impl VirtualPath {
             if !res.pop() {
                 return None;
             }
-            path = &path["../".len()..];
+            path = &path["../".len()..]
         }
         path = path.trim_start_matches("./");
         res.0 = format!("{}/{}", res.0, path);
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index a6a1c73111..9076b93d35 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: ad52054176909945
+lsp_ext.rs hash: c6568e4035333f3a
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 768216fed9..f80b978545 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -136,6 +136,11 @@ Only applies when `#rust-analyzer.completion.addCallParenthesis#` is set.
 --
 Whether to add parenthesis when completing functions.
 --
+[[rust-analyzer.completion.snippets]]rust-analyzer.completion.snippets (default: `{}`)::
++
+--
+Custom completion snippets.
+--
 [[rust-analyzer.completion.postfix.enable]]rust-analyzer.completion.postfix.enable (default: `true`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 4ca93b17bc..d9afdfad1b 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -585,6 +585,11 @@
                     "default": true,
                     "type": "boolean"
                 },
+                "rust-analyzer.completion.snippets": {
+                    "markdownDescription": "Custom completion snippets.",
+                    "default": {},
+                    "type": "object"
+                },
                 "rust-analyzer.completion.postfix.enable": {
                     "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc.",
                     "default": true,
diff --git a/lib/arena/src/lib.rs b/lib/arena/src/lib.rs
index 162d556fb7..1720537cb6 100644
--- a/lib/arena/src/lib.rs
+++ b/lib/arena/src/lib.rs
@@ -63,7 +63,7 @@ impl<T> Eq for Idx<T> {}
 
 impl<T> Hash for Idx<T> {
     fn hash<H: Hasher>(&self, state: &mut H) {
-        self.raw.hash(state);
+        self.raw.hash(state)
     }
 }
 
@@ -71,7 +71,7 @@ impl<T> fmt::Debug for Idx<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let mut type_name = std::any::type_name::<T>();
         if let Some(idx) = type_name.rfind(':') {
-            type_name = &type_name[idx + 1..];
+            type_name = &type_name[idx + 1..]
         }
         write!(f, "Idx::<{}>({})", type_name, self.raw)
     }
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
index d026df6c15..922957f57d 100644
--- a/xtask/src/install.rs
+++ b/xtask/src/install.rs
@@ -13,7 +13,7 @@ const REQUIRED_RUST_VERSION: u32 = 55;
 impl flags::Install {
     pub(crate) fn run(self) -> Result<()> {
         if cfg!(target_os = "macos") {
-            fix_path_for_mac().context("Fix path for mac")?;
+            fix_path_for_mac().context("Fix path for mac")?
         }
         if let Some(server) = self.server() {
             install_server(server).context("install server")?;
@@ -148,7 +148,7 @@ fn install_server(opts: ServerOpt) -> Result<()> {
         eprintln!(
             "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
             REQUIRED_RUST_VERSION,
-        );
+        )
     }
     let features = match opts.malloc {
         Malloc::System => &[][..],
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index d0ddf27b4d..e4f540d27f 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -33,13 +33,15 @@ impl flags::Release {
         let commit = cmd!("git rev-parse HEAD").read()?;
         let changelog_n = read_dir(changelog_dir.as_path())?.len();
 
-        for adoc in [
+        for &adoc in [
             "manual.adoc",
             "generated_assists.adoc",
             "generated_config.adoc",
             "generated_diagnostic.adoc",
             "generated_features.adoc",
-        ] {
+        ]
+        .iter()
+        {
             let src = project_root().join("./docs/user/").join(adoc);
             let dst = website_root.join(adoc);
 


Test patch:
diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs
index 29250dca00..cf6833e56a 100644
--- a/crates/hir_ty/src/tests.rs
+++ b/crates/hir_ty/src/tests.rs
@@ -195,9 +195,10 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
                 mismatch.expected.display_test(&db),
                 mismatch.actual.display_test(&db)
             );
-            match mismatches.remove(&range) {
-                Some(annotation) => assert_eq!(actual, annotation),
-                None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
+            if let Some(annotation) = mismatches.remove(&range) {
+                assert_eq!(actual, annotation);
+            } else {
+                format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual);
             }
         }
         for (expr, mismatch) in inference_result.expr_type_mismatches() {
@@ -214,9 +215,10 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
                 mismatch.expected.display_test(&db),
                 mismatch.actual.display_test(&db)
             );
-            match mismatches.remove(&range) {
-                Some(annotation) => assert_eq!(actual, annotation),
-                None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
+            if let Some(annotation) = mismatches.remove(&range) {
+                assert_eq!(actual, annotation);
+            } else {
+                format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual);
             }
         }
     }
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index 9531be760e..2e074410cd 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -2100,8 +2100,7 @@ fn test() {
 
 #[test]
 fn unselected_projection_in_trait_env_cycle_1() {
-    // This is not a cycle, because the `T: Trait2<T::Item>` bound depends only on the `T: Trait`
-    // bound, not on itself (since only `Trait` can define `Item`).
+    // this is a legitimate cycle
     check_types(
         r#"
 trait Trait {
@@ -2112,7 +2111,7 @@ trait Trait2<T> {}
 
 fn test<T: Trait>() where T: Trait2<T::Item> {
     let x: T::Item = no_matter;
-}                  //^^^^^^^^^ Trait::Item<T>
+}                  //^^^^^^^^^ {unknown}
 "#,
     );
 }
diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs
index 6872e3b8dc..9168956235 100644
--- a/crates/ide_completion/src/tests.rs
+++ b/crates/ide_completion/src/tests.rs
@@ -74,6 +74,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
         group: true,
         skip_glob_imports: true,
     },
+    snippets: Vec::new(),
 };
 
 pub(crate) fn completion_list(ra_fixture: &str) -> String {
@@ -181,13 +182,15 @@ pub(crate) fn check_edit_with_config(
     let mut actual = db.file_text(position.file_id).to_string();
 
     let mut combined_edit = completion.text_edit().to_owned();
-    if let Some(import_text_edit) =
-        completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use))
-    {
-        combined_edit.union(import_text_edit).expect(
-            "Failed to apply completion resolve changes: change ranges overlap, but should not",
-        )
-    }
+    completion
+        .imports_to_add()
+        .iter()
+        .filter_map(|edit| edit.to_text_edit(config.insert_use))
+        .for_each(|text_edit| {
+            combined_edit.union(text_edit).expect(
+                "Failed to apply completion resolve changes: change ranges overlap, but should not",
+            )
+        });
 
     combined_edit.apply(&mut actual);
     assert_eq_text!(&ra_fixture_after, &actual)
diff --git a/crates/syntax/src/tests.rs b/crates/syntax/src/tests.rs
index 022db39f33..f8f7a3ad32 100644
--- a/crates/syntax/src/tests.rs
+++ b/crates/syntax/src/tests.rs
@@ -227,9 +227,12 @@ where
     T: crate::AstNode,
     F: Fn(&str) -> Result<T, ()>,
 {
-    dir_tests(&test_data_dir(), ok_paths, "rast", |text, path| match f(text) {
-        Ok(node) => format!("{:#?}", crate::ast::AstNode::syntax(&node)),
-        Err(_) => panic!("Failed to parse '{:?}'", path),
+    dir_tests(&test_data_dir(), ok_paths, "rast", |text, path| {
+        if let Ok(node) = f(text) {
+            format!("{:#?}", crate::ast::AstNode::syntax(&node))
+        } else {
+            panic!("Failed to parse '{:?}'", path);
+        }
     });
     dir_tests(&test_data_dir(), err_paths, "rast", |text, path| {
         if f(text).is_ok() {
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index d2b5877490..c9f3c2cb8e 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -215,7 +215,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> String {
         .flat_map(|node| node.traits.iter().map(move |t| (t, node)))
         .into_group_map()
         .into_iter()
-        .sorted_by_key(|(k, _)| *k)
+        .sorted_by_key(|(k, _)| k.clone())
         .map(|(trait_name, nodes)| {
             let name = format_ident!("Any{}", trait_name);
             let trait_name = format_ident!("{}", trait_name);
@@ -558,13 +558,12 @@ impl Field {
 }
 
 fn lower(grammar: &Grammar) -> AstSrc {
-    let mut res = AstSrc {
-        tokens: "Whitespace Comment String ByteString IntNumber FloatNumber"
-            .split_ascii_whitespace()
-            .map(|it| it.to_string())
-            .collect::<Vec<_>>(),
-        ..Default::default()
-    };
+    let mut res = AstSrc::default();
+
+    res.tokens = "Whitespace Comment String ByteString IntNumber FloatNumber"
+        .split_ascii_whitespace()
+        .map(|it| it.to_string())
+        .collect::<Vec<_>>();
 
     let nodes = grammar.iter().collect::<Vec<_>>();
 
diff --git a/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rast b/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rast
index 5d5803f751..1a8979db89 100644
--- a/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rast
@@ -1,5 +1,5 @@
-SOURCE_FILE@0..223
-  FN@0..222
+SOURCE_FILE@0..135
+  FN@0..134
     FN_KW@0..2 "fn"
     WHITESPACE@2..3 " "
     NAME@3..7
@@ -8,168 +8,92 @@ SOURCE_FILE@0..223
       L_PAREN@7..8 "("
       R_PAREN@8..9 ")"
     WHITESPACE@9..10 " "
-    BLOCK_EXPR@10..222
-      STMT_LIST@10..222
+    BLOCK_EXPR@10..134
+      STMT_LIST@10..134
         L_CURLY@10..11 "{"
         WHITESPACE@11..16 "\n    "
-        EXPR_STMT@16..132
-          MATCH_EXPR@16..132
-            MATCH_KW@16..21 "match"
-            WHITESPACE@21..22 " "
-            LITERAL@22..24
-              INT_NUMBER@22..24 "92"
-            WHITESPACE@24..25 " "
-            MATCH_ARM_LIST@25..132
-              L_CURLY@25..26 "{"
-              WHITESPACE@26..35 "\n        "
-              MATCH_ARM@35..51
-                RANGE_PAT@35..44
-                  LITERAL_PAT@35..36
-                    LITERAL@35..36
-                      INT_NUMBER@35..36 "0"
-                  WHITESPACE@36..37 " "
-                  DOT3@37..40 "..."
-                  WHITESPACE@40..41 " "
-                  LITERAL_PAT@41..44
-                    LITERAL@41..44
-                      INT_NUMBER@41..44 "100"
-                WHITESPACE@44..45 " "
-                FAT_ARROW@45..47 "=>"
-                WHITESPACE@47..48 " "
-                TUPLE_EXPR@48..50
-                  L_PAREN@48..49 "("
-                  R_PAREN@49..50 ")"
-                COMMA@50..51 ","
-              WHITESPACE@51..60 "\n        "
-              MATCH_ARM@60..78
-                RANGE_PAT@60..71
-                  LITERAL_PAT@60..63
-                    LITERAL@60..63
-                      INT_NUMBER@60..63 "101"
-                  WHITESPACE@63..64 " "
-                  DOT2EQ@64..67 "..="
-                  WHITESPACE@67..68 " "
-                  LITERAL_PAT@68..71
-                    LITERAL@68..71
-                      INT_NUMBER@68..71 "200"
-                WHITESPACE@71..72 " "
-                FAT_ARROW@72..74 "=>"
-                WHITESPACE@74..75 " "
-                TUPLE_EXPR@75..77
-                  L_PAREN@75..76 "("
-                  R_PAREN@76..77 ")"
-                COMMA@77..78 ","
-              WHITESPACE@78..87 "\n        "
-              MATCH_ARM@87..104
-                RANGE_PAT@87..97
-                  LITERAL_PAT@87..90
-                    LITERAL@87..90
-                      INT_NUMBER@87..90 "200"
-                  WHITESPACE@90..91 " "
-                  DOT2@91..93 ".."
-                  WHITESPACE@93..94 " "
-                  LITERAL_PAT@94..97
-                    LITERAL@94..97
-                      INT_NUMBER@94..97 "301"
-                WHITESPACE@97..98 " "
-                FAT_ARROW@98..100 "=>"
-                WHITESPACE@100..101 " "
-                TUPLE_EXPR@101..103
-                  L_PAREN@101..102 "("
-                  R_PAREN@102..103 ")"
-                COMMA@103..104 ","
-              WHITESPACE@104..113 "\n        "
-              MATCH_ARM@113..126
-                RANGE_PAT@113..119
-                  LITERAL_PAT@113..116
-                    LITERAL@113..116
-                      INT_NUMBER@113..116 "302"
-                  WHITESPACE@116..117 " "
-                  DOT2@117..119 ".."
-                WHITESPACE@119..120 " "
-                FAT_ARROW@120..122 "=>"
-                WHITESPACE@122..123 " "
-                TUPLE_EXPR@123..125
-                  L_PAREN@123..124 "("
-                  R_PAREN@124..125 ")"
-                COMMA@125..126 ","
-              WHITESPACE@126..131 "\n    "
-              R_CURLY@131..132 "}"
-        WHITESPACE@132..138 "\n\n    "
-        MATCH_EXPR@138..220
-          MATCH_KW@138..143 "match"
-          WHITESPACE@143..144 " "
-          CALL_EXPR@144..158
-            PATH_EXPR@144..148
-              PATH@144..148
-                PATH_SEGMENT@144..148
-                  NAME_REF@144..148
-                    IDENT@144..148 "Some"
-            ARG_LIST@148..158
-              L_PAREN@148..149 "("
-              CAST_EXPR@149..157
-                LITERAL@149..151
-                  INT_NUMBER@149..151 "10"
-                WHITESPACE@151..152 " "
-                AS_KW@152..154 "as"
-                WHITESPACE@154..155 " "
-                PATH_TYPE@155..157
-                  PATH@155..157
-                    PATH_SEGMENT@155..157
-                      NAME_REF@155..157
-                        IDENT@155..157 "u8"
-              R_PAREN@157..158 ")"
-          WHITESPACE@158..159 " "
-          MATCH_ARM_LIST@159..220
-            L_CURLY@159..160 "{"
-            WHITESPACE@160..169 "\n        "
-            MATCH_ARM@169..190
-              OR_PAT@169..183
-                TUPLE_STRUCT_PAT@169..176
-                  PATH@169..173
-                    PATH_SEGMENT@169..173
-                      NAME_REF@169..173
-                        IDENT@169..173 "Some"
-                  L_PAREN@173..174 "("
-                  LITERAL_PAT@174..175
-                    LITERAL@174..175
-                      INT_NUMBER@174..175 "0"
-                  R_PAREN@175..176 ")"
-                WHITESPACE@176..177 " "
-                PIPE@177..178 "|"
-                WHITESPACE@178..179 " "
-                IDENT_PAT@179..183
-                  NAME@179..183
-                    IDENT@179..183 "None"
-              WHITESPACE@183..184 " "
-              FAT_ARROW@184..186 "=>"
-              WHITESPACE@186..187 " "
-              TUPLE_EXPR@187..189
-                L_PAREN@187..188 "("
-                R_PAREN@188..189 ")"
-              COMMA@189..190 ","
-            WHITESPACE@190..199 "\n        "
-            MATCH_ARM@199..214
-              TUPLE_STRUCT_PAT@199..208
-                PATH@199..203
-                  PATH_SEGMENT@199..203
-                    NAME_REF@199..203
-                      IDENT@199..203 "Some"
-                L_PAREN@203..204 "("
-                RANGE_PAT@204..207
-                  LITERAL_PAT@204..205
-                    LITERAL@204..205
-                      INT_NUMBER@204..205 "1"
-                  DOT2@205..207 ".."
-                R_PAREN@207..208 ")"
-              WHITESPACE@208..209 " "
-              FAT_ARROW@209..211 "=>"
-              WHITESPACE@211..212 " "
-              TUPLE_EXPR@212..214
-                L_PAREN@212..213 "("
-                R_PAREN@213..214 ")"
-            WHITESPACE@214..219 "\n    "
-            R_CURLY@219..220 "}"
-        WHITESPACE@220..221 "\n"
-        R_CURLY@221..222 "}"
-  WHITESPACE@222..223 "\n"
+        MATCH_EXPR@16..132
+          MATCH_KW@16..21 "match"
+          WHITESPACE@21..22 " "
+          LITERAL@22..24
+            INT_NUMBER@22..24 "92"
+          WHITESPACE@24..25 " "
+          MATCH_ARM_LIST@25..132
+            L_CURLY@25..26 "{"
+            WHITESPACE@26..35 "\n        "
+            MATCH_ARM@35..51
+              RANGE_PAT@35..44
+                LITERAL_PAT@35..36
+                  LITERAL@35..36
+                    INT_NUMBER@35..36 "0"
+                WHITESPACE@36..37 " "
+                DOT3@37..40 "..."
+                WHITESPACE@40..41 " "
+                LITERAL_PAT@41..44
+                  LITERAL@41..44
+                    INT_NUMBER@41..44 "100"
+              WHITESPACE@44..45 " "
+              FAT_ARROW@45..47 "=>"
+              WHITESPACE@47..48 " "
+              TUPLE_EXPR@48..50
+                L_PAREN@48..49 "("
+                R_PAREN@49..50 ")"
+              COMMA@50..51 ","
+            WHITESPACE@51..60 "\n        "
+            MATCH_ARM@60..78
+              RANGE_PAT@60..71
+                LITERAL_PAT@60..63
+                  LITERAL@60..63
+                    INT_NUMBER@60..63 "101"
+                WHITESPACE@63..64 " "
+                DOT2EQ@64..67 "..="
+                WHITESPACE@67..68 " "
+                LITERAL_PAT@68..71
+                  LITERAL@68..71
+                    INT_NUMBER@68..71 "200"
+              WHITESPACE@71..72 " "
+              FAT_ARROW@72..74 "=>"
+              WHITESPACE@74..75 " "
+              TUPLE_EXPR@75..77
+                L_PAREN@75..76 "("
+                R_PAREN@76..77 ")"
+              COMMA@77..78 ","
+            WHITESPACE@78..87 "\n        "
+            MATCH_ARM@87..104
+              RANGE_PAT@87..97
+                LITERAL_PAT@87..90
+                  LITERAL@87..90
+                    INT_NUMBER@87..90 "200"
+                WHITESPACE@90..91 " "
+                DOT2@91..93 ".."
+                WHITESPACE@93..94 " "
+                LITERAL_PAT@94..97
+                  LITERAL@94..97
+                    INT_NUMBER@94..97 "301"
+              WHITESPACE@97..98 " "
+              FAT_ARROW@98..100 "=>"
+              WHITESPACE@100..101 " "
+              TUPLE_EXPR@101..103
+                L_PAREN@101..102 "("
+                R_PAREN@102..103 ")"
+              COMMA@103..104 ","
+            WHITESPACE@104..113 "\n        "
+            MATCH_ARM@113..126
+              RANGE_PAT@113..119
+                LITERAL_PAT@113..116
+                  LITERAL@113..116
+                    INT_NUMBER@113..116 "302"
+                WHITESPACE@116..117 " "
+                DOT2@117..119 ".."
+              WHITESPACE@119..120 " "
+              FAT_ARROW@120..122 "=>"
+              WHITESPACE@122..123 " "
+              TUPLE_EXPR@123..125
+                L_PAREN@123..124 "("
+                R_PAREN@124..125 ")"
+              COMMA@125..126 ","
+            WHITESPACE@126..131 "\n    "
+            R_CURLY@131..132 "}"
+        WHITESPACE@132..133 "\n"
+        R_CURLY@133..134 "}"
+  WHITESPACE@134..135 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rs b/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rs
index fa91efd212..3833fbd8d5 100644
--- a/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rs
+++ b/crates/syntax/test_data/parser/inline/ok/0058_range_pat.rs
@@ -5,9 +5,4 @@ fn main() {
         200 .. 301 => (),
         302 .. => (),
     }
-
-    match Some(10 as u8) {
-        Some(0) | None => (),
-        Some(1..) => ()
-    }
 }
diff --git a/crates/test_utils/src/assert_linear.rs b/crates/test_utils/src/assert_linear.rs
index 24502ddb41..6ecc232e1e 100644
--- a/crates/test_utils/src/assert_linear.rs
+++ b/crates/test_utils/src/assert_linear.rs
@@ -43,7 +43,7 @@ impl AssertLinear {
     }
 
     pub fn sample(&mut self, x: f64, y: f64) {
-        self.rounds.last_mut().unwrap().samples.push((x, y));
+        self.rounds.last_mut().unwrap().samples.push((x, y))
     }
 }
 
@@ -54,7 +54,7 @@ impl Drop for AssertLinear {
             for round in &self.rounds {
                 eprintln!("\n{}", round.plot);
             }
-            panic!("Doesn't look linear!");
+            panic!("Doesn't look linear!")
         }
     }
 }
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs
index 5ea7a994b4..f2013b34c5 100644
--- a/crates/test_utils/src/fixture.rs
+++ b/crates/test_utils/src/fixture.rs
@@ -142,14 +142,14 @@ impl Fixture {
 
             if line.starts_with("//-") {
                 let meta = Fixture::parse_meta_line(line);
-                res.push(meta);
+                res.push(meta)
             } else {
                 if line.starts_with("// ")
                     && line.contains(':')
                     && !line.contains("::")
                     && line.chars().all(|it| !it.is_uppercase())
                 {
-                    panic!("looks like invalid metadata line: {:?}", line);
+                    panic!("looks like invalid metadata line: {:?}", line)
                 }
 
                 if let Some(entry) = res.last_mut() {
@@ -256,9 +256,9 @@ impl MiniCore {
         let line = line.strip_prefix("//- minicore:").unwrap().trim();
         for entry in line.split(", ") {
             if res.has_flag(entry) {
-                panic!("duplicate minicore flag: {:?}", entry);
+                panic!("duplicate minicore flag: {:?}", entry)
             }
-            res.activated_flags.push(entry.to_string());
+            res.activated_flags.push(entry.to_string())
         }
 
         res
@@ -310,7 +310,7 @@ impl MiniCore {
         // Fixed point loop to compute transitive closure of flags.
         loop {
             let mut changed = false;
-            for &(u, v) in &implications {
+            for &(u, v) in implications.iter() {
                 if self.has_flag(u) && !self.has_flag(v) {
                     self.activated_flags.push(v.to_string());
                     changed = true;
@@ -354,7 +354,7 @@ impl MiniCore {
             }
 
             if keep {
-                buf.push_str(line);
+                buf.push_str(line)
             }
             if line_region {
                 active_regions.pop().unwrap();
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 146e4f0c30..7b43fb9de5 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -244,7 +244,7 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> {
 
                             range + line_start.1
                         };
-                        res.push((range, content));
+                        res.push((range, content))
                     }
                     LineAnnotation::Continuation { mut offset, content } => {
                         offset += annotation_offset;
@@ -301,7 +301,7 @@ fn extract_line_annotations(mut line: &str) -> Vec<LineAnnotation> {
         let mut file = false;
         if !continuation && content.starts_with("file") {
             file = true;
-            content = &content["file".len()..];
+            content = &content["file".len()..]
         }
 
         let content = content.trim().to_string();
@@ -371,7 +371,7 @@ fn main() {
 pub fn skip_slow_tests() -> bool {
     let should_skip = std::env::var("CI").is_err() && std::env::var("RUN_SLOW_TESTS").is_err();
     if should_skip {
-        eprintln!("ignoring slow test");
+        eprintln!("ignoring slow test")
     } else {
         let path = project_root().join("./target/.slow_tests_cookie");
         fs::write(&path, ".").unwrap();
@@ -432,7 +432,7 @@ pub fn bench(label: &'static str) -> impl Drop {
 
     impl Drop for Bencher {
         fn drop(&mut self) {
-            eprintln!("{}: {}", self.label, self.sw.elapsed());
+            eprintln!("{}: {}", self.label, self.sw.elapsed())
         }
     }