Repository: rust-lang/rust-analyzer
Pull request #17227 (2024-05-14 09:58:09)

fix: Hash file contents to verify whether file actually changed
Fixes https://github.com/rust-lang/rust-analyzer/issues/16580


Patch:
diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs
index 2f7ebbfec1..ac0caaf0dc 100644
--- a/crates/hir-def/src/hir.rs
+++ b/crates/hir-def/src/hir.rs
@@ -136,15 +136,15 @@ impl From<ast::LiteralKind> for Literal {
                 Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty)
             }
             LiteralKind::ByteString(bs) => {
-                let text = bs.value().map_or_else(|_| Default::default(), Box::from);
+                let text = bs.value().map(Box::from).unwrap_or_else(Default::default);
                 Literal::ByteString(text)
             }
             LiteralKind::String(s) => {
-                let text = s.value().map_or_else(|_| Default::default(), Box::from);
+                let text = s.value().map(Box::from).unwrap_or_else(Default::default);
                 Literal::String(text)
             }
             LiteralKind::CString(s) => {
-                let text = s.value().map_or_else(|_| Default::default(), Box::from);
+                let text = s.value().map(Box::from).unwrap_or_else(Default::default);
                 Literal::CString(text)
             }
             LiteralKind::Byte(b) => {
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 80064e18fa..acda64c41f 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -29,6 +29,7 @@
 //!
 //! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its
 //! surface syntax.
+#![allow(unexpected_cfgs)]
 
 mod lower;
 mod pretty;
@@ -467,13 +468,12 @@ macro_rules! mod_items {
         pub enum GenericModItem {
             $(
                 $(
-                    #[cfg_attr(FALSE, $generic_params)]
+                    #[cfg_attr(ignore_fragment, $generic_params)]
                     $typ(FileItemTreeId<$typ>),
                 )?
             )+
         }
 
-        #[allow(unexpected_cfgs)]
         impl From<GenericModItem> for ModItem {
             fn from(id: GenericModItem) -> ModItem {
                 match id {
@@ -494,7 +494,6 @@ macro_rules! mod_items {
         }
 
         $(
-            #[allow(unexpected_cfgs)]
             impl From<FileItemTreeId<$typ>> for ModItem {
                 fn from(id: FileItemTreeId<$typ>) -> ModItem {
                     ModItem::$typ(id)
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index ba96ab6cc2..4d6fe6db39 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -441,21 +441,21 @@ fn unquote_str(lit: &tt::Literal) -> Option<(String, Span)> {
     let span = lit.span;
     let lit = ast::make::tokens::literal(&lit.to_string());
     let token = ast::String::cast(lit)?;
-    token.value().ok().map(|it| (it.into_owned(), span))
+    token.value().map(|it| (it.into_owned(), span))
 }
 
 fn unquote_char(lit: &tt::Literal) -> Option<(char, Span)> {
     let span = lit.span;
     let lit = ast::make::tokens::literal(&lit.to_string());
     let token = ast::Char::cast(lit)?;
-    token.value().ok().zip(Some(span))
+    token.value().zip(Some(span))
 }
 
 fn unquote_byte_string(lit: &tt::Literal) -> Option<(Vec<u8>, Span)> {
     let span = lit.span;
     let lit = ast::make::tokens::literal(&lit.to_string());
     let token = ast::ByteString::cast(lit)?;
-    token.value().ok().map(|it| (it.into_owned(), span))
+    token.value().map(|it| (it.into_owned(), span))
 }
 
 fn compile_error_expand(
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 9fef3e0575..079aa90600 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2593,20 +2593,6 @@ impl BuiltinType {
         matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_))
     }
 
-    pub fn is_f32(&self) -> bool {
-        matches!(
-            self.inner,
-            hir_def::builtin_type::BuiltinType::Float(hir_def::builtin_type::BuiltinFloat::F32)
-        )
-    }
-
-    pub fn is_f64(&self) -> bool {
-        matches!(
-            self.inner,
-            hir_def::builtin_type::BuiltinType::Float(hir_def::builtin_type::BuiltinFloat::F64)
-        )
-    }
-
     pub fn is_char(&self) -> bool {
         matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
     }
diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs
index 5c5ddae19e..93e7300491 100644
--- a/crates/hir/src/term_search.rs
+++ b/crates/hir/src/term_search.rs
@@ -127,13 +127,6 @@ impl LookupTable {
             self.types_wishlist.insert(ty.clone());
         }
 
-        // Collapse suggestions if there are many
-        if let Some(res) = &res {
-            if res.len() > self.many_threshold {
-                return Some(vec![Expr::Many(ty.clone())]);
-            }
-        }
-
         res
     }
 
@@ -165,13 +158,6 @@ impl LookupTable {
             self.types_wishlist.insert(ty.clone());
         }
 
-        // Collapse suggestions if there are many
-        if let Some(res) = &res {
-            if res.len() > self.many_threshold {
-                return Some(vec![Expr::Many(ty.clone())]);
-            }
-        }
-
         res
     }
 
@@ -269,13 +255,13 @@ pub struct TermSearchConfig {
     pub enable_borrowcheck: bool,
     /// Indicate when to squash multiple trees to `Many` as there are too many to keep track
     pub many_alternatives_threshold: usize,
-    /// Fuel for term search in "units of work"
-    pub fuel: u64,
+    /// Depth of the search eg. number of cycles to run
+    pub depth: usize,
 }
 
 impl Default for TermSearchConfig {
     fn default() -> Self {
-        Self { enable_borrowcheck: true, many_alternatives_threshold: 1, fuel: 400 }
+        Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 }
     }
 }
 
@@ -294,7 +280,8 @@ impl Default for TermSearchConfig {
 ///    transformation tactics. For example functions take as from set of types (arguments) to some
 ///    type (return type). Other transformations include methods on type, type constructors and
 ///    projections to struct fields (field access).
-/// 3. If we run out of fuel (term search takes too long) we stop iterating.
+/// 3. Once we manage to find path to type we are interested in we continue for single round to see
+///    if we can find more paths that take us to the `goal` type.
 /// 4. Return all the paths (type trees) that take us to the `goal` type.
 ///
 /// Note that there are usually more ways we can get to the `goal` type but some are discarded to
@@ -310,31 +297,21 @@ pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
     });
 
     let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone());
-    let fuel = std::cell::Cell::new(ctx.config.fuel);
-
-    let should_continue = &|| {
-        let remaining = fuel.get();
-        fuel.set(remaining.saturating_sub(1));
-        if remaining == 0 {
-            tracing::debug!("fuel exhausted");
-        }
-        remaining > 0
-    };
 
     // Try trivial tactic first, also populates lookup table
     let mut solutions: Vec<Expr> = tactics::trivial(ctx, &defs, &mut lookup).collect();
     // Use well known types tactic before iterations as it does not depend on other tactics
     solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
 
-    while should_continue() {
+    for _ in 0..ctx.config.depth {
         lookup.new_round();
 
-        solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup, should_continue));
-        solutions.extend(tactics::free_function(ctx, &defs, &mut lookup, should_continue));
-        solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup, should_continue));
-        solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup, should_continue));
-        solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup, should_continue));
-        solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup, should_continue));
+        solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup));
+        solutions.extend(tactics::free_function(ctx, &defs, &mut lookup));
+        solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup));
+        solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup));
+        solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup));
+        solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup));
 
         // Discard not interesting `ScopeDef`s for speedup
         for def in lookup.exhausted_scopedefs() {
diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs
index 9f56a1ee55..2d0c5630e1 100644
--- a/crates/hir/src/term_search/expr.rs
+++ b/crates/hir/src/term_search/expr.rs
@@ -211,13 +211,13 @@ impl Expr {
                 }
             }
             Expr::Method { func, target, params, .. } => {
-                if self.contains_many_in_illegal_pos(db) {
+                if target.contains_many_in_illegal_pos() {
                     return Ok(many_formatter(&target.ty(db)));
                 }
 
                 let func_name = func.name(db).display(db.upcast()).to_string();
                 let self_param = func.self_param(db).unwrap();
-                let target_str = target.gen_source_code(
+                let target = target.gen_source_code(
                     sema_scope,
                     many_formatter,
                     prefer_no_std,
@@ -236,12 +236,9 @@ impl Expr {
                     Some(trait_) => {
                         let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?;
                         let target = match self_param.access(db) {
-                            crate::Access::Shared if !target.is_many() => format!("&{target_str}"),
-                            crate::Access::Exclusive if !target.is_many() => {
-                                format!("&mut {target_str}")
-                            }
-                            crate::Access::Owned => target_str,
-                            _ => many_formatter(&target.ty(db)),
+                            crate::Access::Shared => format!("&{target}"),
+                            crate::Access::Exclusive => format!("&mut {target}"),
+                            crate::Access::Owned => target,
                         };
                         let res = match args.is_empty() {
                             true => format!("{trait_name}::{func_name}({target})",),
@@ -249,7 +246,7 @@ impl Expr {
                         };
                         Ok(res)
                     }
-                    None => Ok(format!("{target_str}.{func_name}({args})")),
+                    None => Ok(format!("{target}.{func_name}({args})")),
                 }
             }
             Expr::Variant { variant, generics, params } => {
@@ -384,7 +381,7 @@ impl Expr {
                 Ok(res)
             }
             Expr::Field { expr, field } => {
-                if expr.contains_many_in_illegal_pos(db) {
+                if expr.contains_many_in_illegal_pos() {
                     return Ok(many_formatter(&expr.ty(db)));
                 }
 
@@ -398,7 +395,7 @@ impl Expr {
                 Ok(format!("{strukt}.{field}"))
             }
             Expr::Reference(expr) => {
-                if expr.contains_many_in_illegal_pos(db) {
+                if expr.contains_many_in_illegal_pos() {
                     return Ok(many_formatter(&expr.ty(db)));
                 }
 
@@ -469,15 +466,10 @@ impl Expr {
     /// macro!().bar()
     /// &macro!()
     /// ```
-    fn contains_many_in_illegal_pos(&self, db: &dyn HirDatabase) -> bool {
+    fn contains_many_in_illegal_pos(&self) -> bool {
         match self {
-            Expr::Method { target, func, .. } => {
-                match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) {
-                    Some(_) => false,
-                    None => target.is_many(),
-                }
-            }
-            Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(db),
+            Expr::Method { target, .. } => target.contains_many_in_illegal_pos(),
+            Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(),
             Expr::Reference(target) => target.is_many(),
             Expr::Many(_) => true,
             _ => false,
diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs
index a26728272d..63b2a2506f 100644
--- a/crates/hir/src/term_search/tactics.rs
+++ b/crates/hir/src/term_search/tactics.rs
@@ -4,7 +4,6 @@
 //! * `ctx` - Context for the term search
 //! * `defs` - Set of items in scope at term search target location
 //! * `lookup` - Lookup table for types
-//! * `should_continue` - Function that indicates when to stop iterating
 //! And they return iterator that yields type trees that unify with the `goal` type.
 
 use std::iter;
@@ -98,19 +97,16 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn type_constructor<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
     fn variant_helper(
         db: &dyn HirDatabase,
         lookup: &mut LookupTable,
-        should_continue: &dyn std::ops::Fn() -> bool,
         parent_enum: Enum,
         variant: Variant,
         config: &TermSearchConfig,
@@ -156,7 +152,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
             .chain((non_default_type_params_len == 0).then_some(Vec::new()));
 
         generic_params
-            .filter(|_| should_continue())
             .filter_map(move |generics| {
                 // Insert default type params
                 let mut g = generics.into_iter();
@@ -199,14 +194,8 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
     defs.iter()
         .filter_map(move |def| match def {
             ScopeDef::ModuleDef(ModuleDef::Variant(it)) => {
-                let variant_exprs = variant_helper(
-                    db,
-                    lookup,
-                    should_continue,
-                    it.parent_enum(db),
-                    *it,
-                    &ctx.config,
-                );
+                let variant_exprs =
+                    variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.config);
                 if variant_exprs.is_empty() {
                     return None;
                 }
@@ -224,9 +213,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
                 let exprs: Vec<(Type, Vec<Expr>)> = enum_
                     .variants(db)
                     .into_iter()
-                    .flat_map(|it| {
-                        variant_helper(db, lookup, should_continue, *enum_, it, &ctx.config)
-                    })
+                    .flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.config))
                     .collect();
 
                 if exprs.is_empty() {
@@ -284,7 +271,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
                     .chain((non_default_type_params_len == 0).then_some(Vec::new()));
 
                 let exprs = generic_params
-                    .filter(|_| should_continue())
                     .filter_map(|generics| {
                         // Insert default type params
                         let mut g = generics.into_iter();
@@ -359,12 +345,10 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn free_function<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
@@ -406,7 +390,6 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
                     .permutations(non_default_type_params_len);
 
                 let exprs: Vec<_> = generic_params
-                    .filter(|_| should_continue())
                     .filter_map(|generics| {
                         // Insert default type params
                         let mut g = generics.into_iter();
@@ -491,12 +474,10 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn impl_method<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     _defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
@@ -573,7 +554,6 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
                 .permutations(non_default_fn_type_params_len);
 
             let exprs: Vec<_> = generic_params
-                .filter(|_| should_continue())
                 .filter_map(|generics| {
                     // Insert default type params
                     let mut g = generics.into_iter();
@@ -665,12 +645,10 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn struct_projection<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     _defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
@@ -678,7 +656,6 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>(
         .new_types(NewTypesKey::StructProjection)
         .into_iter()
         .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup")))
-        .filter(|_| should_continue())
         .flat_map(move |(ty, targets)| {
             ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| {
                 if !field.is_visible_from(db, module) {
@@ -739,12 +716,10 @@ pub(super) fn famous_types<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn impl_static_method<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     _defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
@@ -753,7 +728,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
         .clone()
         .into_iter()
         .chain(iter::once(ctx.goal.clone()))
-        .filter(|_| should_continue())
         .flat_map(|ty| {
             Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
         })
@@ -827,7 +801,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
                 .permutations(non_default_fn_type_params_len);
 
             let exprs: Vec<_> = generic_params
-                .filter(|_| should_continue())
                 .filter_map(|generics| {
                     // Insert default type params
                     let mut g = generics.into_iter();
@@ -911,12 +884,10 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
 /// * `ctx` - Context for the term search
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
-/// * `should_continue` - Function that indicates when to stop iterating
 pub(super) fn make_tuple<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     _defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
-    should_continue: &'a dyn std::ops::Fn() -> bool,
 ) -> impl Iterator<Item = Expr> + 'a {
     let db = ctx.sema.db;
     let module = ctx.scope.module();
@@ -925,7 +896,6 @@ pub(super) fn make_tuple<'a, DB: HirDatabase>(
         .types_wishlist()
         .clone()
         .into_iter()
-        .filter(|_| should_continue())
         .filter(|ty| ty.is_tuple())
         .filter_map(move |ty| {
             // Double check to not contain unknown
@@ -945,7 +915,6 @@ pub(super) fn make_tuple<'a, DB: HirDatabase>(
             let exprs: Vec<Expr> = param_exprs
                 .into_iter()
                 .multi_cartesian_product()
-                .filter(|_| should_continue())
                 .map(|params| {
                     let tys: Vec<Type> = params.iter().map(|it| it.ty(db)).collect();
                     let tuple_ty = Type::new_tuple(module.krate().into(), &tys);
diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs
index 5d76cb0432..fbe17dbfd7 100644
--- a/crates/ide-assists/src/assist_config.rs
+++ b/crates/ide-assists/src/assist_config.rs
@@ -16,5 +16,4 @@ pub struct AssistConfig {
     pub prefer_no_std: bool,
     pub prefer_prelude: bool,
     pub assist_emit_must_use: bool,
-    pub term_search_fuel: u64,
 }
diff --git a/crates/ide-assists/src/handlers/apply_demorgan.rs b/crates/ide-assists/src/handlers/apply_demorgan.rs
index f178a7e0ce..55e0d7f3b2 100644
--- a/crates/ide-assists/src/handlers/apply_demorgan.rs
+++ b/crates/ide-assists/src/handlers/apply_demorgan.rs
@@ -8,7 +8,8 @@ use ide_db::{
 };
 use syntax::{
     ast::{self, make, AstNode, Expr::BinExpr, HasArgList},
-    ted, SyntaxKind, T,
+    ted::{self, Position},
+    SyntaxKind,
 };
 
 use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
@@ -61,7 +62,7 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
     let demorganed = bin_expr.clone_subtree().clone_for_update();
 
     ted::replace(demorganed.op_token()?, ast::make::token(inv_token));
-    let mut exprs = VecDeque::from([
+    let mut exprs = VecDeque::from(vec![
         (bin_expr.lhs()?, demorganed.lhs()?),
         (bin_expr.rhs()?, demorganed.rhs()?),
     ]);
@@ -92,38 +93,58 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
         }
     }
 
+    let dm_lhs = demorganed.lhs()?;
+
     acc.add_group(
         &GroupLabel("Apply De Morgan's law".to_owned()),
         AssistId("apply_demorgan", AssistKind::RefactorRewrite),
         "Apply De Morgan's law",
         op_range,
         |edit| {
-            let demorganed = ast::Expr::BinExpr(demorganed);
             let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast);
             let neg_expr = paren_expr
                 .clone()
                 .and_then(|paren_expr| paren_expr.syntax().parent())
                 .and_then(ast::PrefixExpr::cast)
-                .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
-                .map(ast::Expr::PrefixExpr);
+                .and_then(|prefix_expr| {
+                    if prefix_expr.op_kind()? == ast::UnaryOp::Not {
+                        Some(prefix_expr)
+                    } else {
+                        None
+                    }
+                });
 
             if let Some(paren_expr) = paren_expr {
                 if let Some(neg_expr) = neg_expr {
                     cov_mark::hit!(demorgan_double_negation);
-                    let parent = neg_expr.syntax().parent();
-
-                    if parent.is_some_and(|parent| demorganed.needs_parens_in(parent)) {
-                        cov_mark::hit!(demorgan_keep_parens_for_op_precedence2);
-                        edit.replace_ast(neg_expr, make::expr_paren(demorganed));
-                    } else {
-                        edit.replace_ast(neg_expr, demorganed);
-                    };
+                    edit.replace_ast(ast::Expr::PrefixExpr(neg_expr), demorganed.into());
                 } else {
                     cov_mark::hit!(demorgan_double_parens);
-                    edit.replace_ast(paren_expr.into(), add_bang_paren(demorganed));
+                    ted::insert_all_raw(
+                        Position::before(dm_lhs.syntax()),
+                        vec![
+                            syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)),
+                            syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)),
+                        ],
+                    );
+
+                    ted::append_child_raw(
+                        demorganed.syntax(),
+                        syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::R_PAREN)),
+                    );
+
+                    edit.replace_ast(ast::Expr::ParenExpr(paren_expr), demorganed.into());
                 }
             } else {
-                edit.replace_ast(bin_expr.into(), add_bang_paren(demorganed));
+                ted::insert_all_raw(
+                    Position::before(dm_lhs.syntax()),
+                    vec![
+                        syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)),
+                        syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)),
+                    ],
+                );
+                ted::append_child_raw(demorganed.syntax(), ast::make::token(SyntaxKind::R_PAREN));
+                edit.replace_ast(bin_expr, demorganed);
             }
         },
     )
@@ -250,11 +271,6 @@ fn tail_cb_impl(edit: &mut SourceChangeBuilder, e: &ast::Expr) {
     }
 }
 
-/// Add bang and parentheses to the expression.
-fn add_bang_paren(expr: ast::Expr) -> ast::Expr {
-    make::expr_prefix(T![!], make::expr_paren(expr))
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -333,14 +349,16 @@ fn f() { !(S <= S || S < S) }
         check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
     }
 
-    #[test]
-    fn demorgan_doesnt_hang() {
-        check_assist(
-            apply_demorgan,
-            "fn f() { 1 || 3 &&$0 4 || 5 }",
-            "fn f() { 1 || !(!3 || !4) || 5 }",
-        )
-    }
+    // FIXME : This needs to go.
+    // // https://github.com/rust-lang/rust-analyzer/issues/10963
+    // #[test]
+    // fn demorgan_doesnt_hang() {
+    //     check_assist(
+    //         apply_demorgan,
+    //         "fn f() { 1 || 3 &&$0 4 || 5 }",
+    //         "fn f() { !(!1 || !3 || !4) || 5 }",
+    //     )
+    // }
 
     #[test]
     fn demorgan_keep_pars_for_op_precedence() {
@@ -357,21 +375,6 @@ fn f() { !(S <= S || S < S) }
         );
     }
 
-    #[test]
-    fn demorgan_keep_pars_for_op_precedence2() {
-        cov_mark::check!(demorgan_keep_parens_for_op_precedence2);
-        check_assist(
-            apply_demorgan,
-            "fn f() { (a && !(b &&$0 c); }",
-            "fn f() { (a && (!b || !c); }",
-        );
-    }
-
-    #[test]
-    fn demorgan_keep_pars_for_op_precedence3() {
-        check_assist(apply_demorgan, "fn f() { (a || !(b &&$0 c); }", "fn f() { (a || !b || !c; }");
-    }
-
     #[test]
     fn demorgan_removes_pars_in_eq_precedence() {
         check_assist(
@@ -381,11 +384,6 @@ fn f() { !(S <= S || S < S) }
         )
     }
 
-    #[test]
-    fn demorgan_removes_pars_for_op_precedence2() {
-        check_assist(apply_demorgan, "fn f() { (a || !(b ||$0 c); }", "fn f() { (a || !b && !c; }");
-    }
-
     #[test]
     fn demorgan_iterator_any_all_reverse() {
         check_assist(
diff --git a/crates/ide-assists/src/handlers/raw_string.rs b/crates/ide-assists/src/handlers/raw_string.rs
index 5a197f23d0..63db606336 100644
--- a/crates/ide-assists/src/handlers/raw_string.rs
+++ b/crates/ide-assists/src/handlers/raw_string.rs
@@ -25,7 +25,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
     if token.is_raw() {
         return None;
     }
-    let value = token.value().ok()?;
+    let value = token.value()?;
     let target = token.syntax().text_range();
     acc.add(
         AssistId("make_raw_string", AssistKind::RefactorRewrite),
@@ -64,7 +64,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
     if !token.is_raw() {
         return None;
     }
-    let value = token.value().ok()?;
+    let value = token.value()?;
     let target = token.syntax().text_range();
     acc.add(
         AssistId("make_usual_string", AssistKind::RefactorRewrite),
@@ -398,12 +398,12 @@ string"###;
     }
 
     #[test]
-    fn remove_hash_does_not_work() {
+    fn remove_hash_doesnt_work() {
         check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
     }
 
     #[test]
-    fn remove_hash_no_hash_does_not_work() {
+    fn remove_hash_no_hash_doesnt_work() {
         check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
     }
 
diff --git a/crates/ide-assists/src/handlers/replace_string_with_char.rs b/crates/ide-assists/src/handlers/replace_string_with_char.rs
index a48b20acbc..6310981ccc 100644
--- a/crates/ide-assists/src/handlers/replace_string_with_char.rs
+++ b/crates/ide-assists/src/handlers/replace_string_with_char.rs
@@ -25,7 +25,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // ```
 pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
-    let value = token.value().ok()?;
+    let value = token.value()?;
     let target = token.syntax().text_range();
 
     if value.chars().take(2).count() != 1 {
diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs
index d0c6ae2198..0f4a8e3aec 100644
--- a/crates/ide-assists/src/handlers/term_search.rs
+++ b/crates/ide-assists/src/handlers/term_search.rs
@@ -1,5 +1,5 @@
 //! Term search assist
-use hir::term_search::{TermSearchConfig, TermSearchCtx};
+use hir::term_search::TermSearchCtx;
 use ide_db::{
     assists::{AssistId, AssistKind, GroupLabel},
     famous_defs::FamousDefs,
@@ -34,7 +34,7 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
         sema: &ctx.sema,
         scope: &scope,
         goal: target_ty,
-        config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() },
+        config: Default::default(),
     };
     let paths = hir::term_search::term_search(&term_search_ctx);
 
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index 1e31d65fdd..802e9bc3a8 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -353,7 +353,7 @@ pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>)
             config: hir::term_search::TermSearchConfig {
                 enable_borrowcheck: false,
                 many_alternatives_threshold: 1,
-                fuel: 200,
+                depth: 6,
             },
         };
         let exprs = hir::term_search::term_search(&term_search_ctx);
diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs
index 809c305ed8..04563fb0f4 100644
--- a/crates/ide-completion/src/config.rs
+++ b/crates/ide-completion/src/config.rs
@@ -15,7 +15,6 @@ pub struct CompletionConfig {
     pub enable_self_on_the_fly: bool,
     pub enable_private_editable: bool,
     pub enable_term_search: bool,
-    pub term_search_fuel: u64,
     pub full_function_signatures: bool,
     pub callable: Option<CallableSnippets>,
     pub snippet_cap: Option<SnippetCap>,
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 7fa31e2757..db69b22078 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -1853,7 +1853,8 @@ fn f() { A { bar: b$0 }; }
                 fn baz() [type]
                 ex baz() [type]
                 ex bar() [type]
-                ex A { bar: ... }.bar [type]
+                ex A { bar: baz() }.bar [type]
+                ex A { bar: bar() }.bar [type]
                 st A []
                 fn f() []
             "#]],
@@ -1991,8 +1992,8 @@ fn main() {
 }
             "#,
             expect![[r#"
-                ex core::ops::Deref::deref(&t) (use core::ops::Deref) [type_could_unify]
                 ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify]
+                ex core::ops::Deref::deref(&t) (use core::ops::Deref) [type_could_unify]
                 lc m [local]
                 lc t [local]
                 lc &t [type+local]
@@ -2041,8 +2042,8 @@ fn main() {
 }
             "#,
             expect![[r#"
-                ex core::ops::DerefMut::deref_mut(&mut t) (use core::ops::DerefMut) [type_could_unify]
                 ex core::ops::DerefMut::deref_mut(&mut T(S)) (use core::ops::DerefMut) [type_could_unify]
+                ex core::ops::DerefMut::deref_mut(&mut t) (use core::ops::DerefMut) [type_could_unify]
                 lc m [local]
                 lc t [local]
                 lc &mut t [type+local]
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index 656d79dc73..56c8181e84 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -1,6 +1,6 @@
 use hir::{
     db::ExpandDatabase,
-    term_search::{term_search, TermSearchConfig, TermSearchCtx},
+    term_search::{term_search, TermSearchCtx},
     ClosureStyle, HirDisplay,
 };
 use ide_db::{
@@ -47,7 +47,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
         sema: &ctx.sema,
         scope: &scope,
         goal: d.expected.clone(),
-        config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() },
+        config: Default::default(),
     };
     let paths = term_search(&term_search_ctx);
 
@@ -274,7 +274,7 @@ impl Foo for Baz {
 }
 fn asd() -> Bar {
     let a = Baz;
-    Foo::foo(_)
+    Foo::foo(a)
 }
 ",
         );
@@ -363,7 +363,7 @@ impl Foo for A {
 }
 fn main() {
     let a = A;
-    let c: Bar = Foo::foo(_);
+    let c: Bar = Foo::foo(&a);
 }"#,
         );
     }
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 15543a5d65..54fde5d839 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -232,7 +232,6 @@ pub struct DiagnosticsConfig {
     pub insert_use: InsertUseConfig,
     pub prefer_no_std: bool,
     pub prefer_prelude: bool,
-    pub term_search_fuel: u64,
 }
 
 impl DiagnosticsConfig {
@@ -257,7 +256,6 @@ impl DiagnosticsConfig {
             },
             prefer_no_std: false,
             prefer_prelude: true,
-            term_search_fuel: 400,
         }
     }
 }
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 76b80fcefa..ddeeca5f7b 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -123,7 +123,7 @@ fn try_lookup_include_path(
     {
         return None;
     }
-    let path = token.value().ok()?;
+    let path = token.value()?;
 
     let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
     let size = sema.db.file_text(file_id).len().try_into().ok()?;
@@ -179,11 +179,11 @@ fn try_filter_trait_item_definition(
         AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
             let trait_ = assoc.implemented_trait(db)?;
             let name = def.name(db)?;
-            let discriminant_value = discriminant(&assoc);
+            let discri_value = discriminant(&assoc);
             trait_
                 .items(db)
                 .iter()
-                .filter(|itm| discriminant(*itm) == discriminant_value)
+                .filter(|itm| discriminant(*itm) == discri_value)
                 .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
                 .map(|it| it.collect())
         }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index bdb56081c3..f9210da5fd 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -15,7 +15,7 @@ use ide_db::{
     FxIndexSet, RootDatabase,
 };
 use itertools::{multizip, Itertools};
-use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
+use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T};
 
 use crate::{
     doc_links::token_as_doc_comment,
@@ -298,8 +298,61 @@ fn hover_simple(
         })
         // tokens
         .or_else(|| {
-            render::literal(sema, original_token.clone())
-                .map(|markup| HoverResult { markup, actions: vec![] })
+            let mut res = HoverResult::default();
+            match_ast! {
+                match original_token {
+                    ast::String(string) => {
+                        res.markup = Markup::fenced_block_text(format_args!("{}", string.value()?));
+                    },
+                    ast::ByteString(string) => {
+                        res.markup = Markup::fenced_block_text(format_args!("{:?}", string.value()?));
+                    },
+                    ast::CString(string) => {
+                        let val = string.value()?;
+                        res.markup = Markup::fenced_block_text(format_args!("{}", std::str::from_utf8(val.as_ref()).ok()?));
+                    },
+                    ast::Char(char) => {
+                        let mut res = HoverResult::default();
+                        res.markup = Markup::fenced_block_text(format_args!("{}", char.value()?));
+                    },
+                    ast::Byte(byte) => {
+                        res.markup = Markup::fenced_block_text(format_args!("0x{:X}", byte.value()?));
+                    },
+                    ast::FloatNumber(num) => {
+                        res.markup = if num.suffix() == Some("f32") {
+                            match num.value_f32() {
+                                Ok(num) => {
+                                    Markup::fenced_block_text(format_args!("{num} (bits: 0x{:X})", num.to_bits()))
+                                },
+                                Err(e) => {
+                                    Markup::fenced_block_text(format_args!("{e}"))
+                                },
+                            }
+                        } else {
+                            match num.value() {
+                                Ok(num) => {
+                                    Markup::fenced_block_text(format_args!("{num} (bits: 0x{:X})", num.to_bits()))
+                                },
+                                Err(e) => {
+                                    Markup::fenced_block_text(format_args!("{e}"))
+                                },
+                            }
+                        };
+                    },
+                    ast::IntNumber(num) => {
+                        res.markup = match num.value() {
+                            Ok(num) => {
+                                Markup::fenced_block_text(format_args!("{num} (0x{num:X}|0b{num:b})"))
+                            },
+                            Err(e) => {
+                                Markup::fenced_block_text(format_args!("{e}"))
+                            },
+                        };
+                    },
+                    _ => return None
+                }
+            }
+            Some(res)
         });
 
     result.map(|mut res: HoverResult| {
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index a166bab37c..ee96767411 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -17,7 +17,11 @@ use ide_db::{
 };
 use itertools::Itertools;
 use stdx::format_to;
-use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T};
+use syntax::{
+    algo,
+    ast::{self, RecordPat},
+    match_ast, AstNode, Direction, SyntaxToken, T,
+};
 
 use crate::{
     doc_links::{remove_links, rewrite_links},
@@ -272,7 +276,7 @@ pub(super) fn keyword(
 pub(super) fn struct_rest_pat(
     sema: &Semantics<'_, RootDatabase>,
     _config: &HoverConfig,
-    pattern: &ast::RecordPat,
+    pattern: &RecordPat,
 ) -> HoverResult {
     let missing_fields = sema.record_pattern_missing_fields(pattern);
 
@@ -522,60 +526,6 @@ pub(super) fn definition(
     markup(docs.map(Into::into), desc, mod_path)
 }
 
-pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Markup> {
-    let lit = token.parent().and_then(ast::Literal::cast)?;
-    let ty = if let Some(p) = lit.syntax().parent().and_then(ast::Pat::cast) {
-        sema.type_of_pat(&p)?
-    } else {
-        sema.type_of_expr(&ast::Expr::Literal(lit))?
-    }
-    .original;
-
-    let value = match_ast! {
-        match token {
-            ast::String(string)     => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
-            ast::ByteString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("{it:?}")),
-            ast::CString(string)    => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| std::str::from_utf8(it).map_or_else(|e| format!("{e:?}"), ToOwned::to_owned)),
-            ast::Char(char)         => char  .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
-            ast::Byte(byte)         => byte  .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")),
-            ast::FloatNumber(num) => {
-                let (text, _) = num.split_into_parts();
-                let text = text.replace('_', "");
-                if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) {
-                    match text.parse::<f32>() {
-                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
-                        Err(e) => Err(e.to_string()),
-                    }
-                } else {
-                    match text.parse::<f64>() {
-                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
-                        Err(e) => Err(e.to_string()),
-                    }
-                }
-            },
-            ast::IntNumber(num) => match num.value() {
-                Ok(num) => Ok(format!("{num} (0x{num:X}|0b{num:b})")),
-                Err(e) => Err(e.to_string()),
-            },
-            _ => return None
-        }
-    };
-    let ty = ty.display(sema.db);
-
-    let mut s = format!("```rust\n{ty}\n```\n___\n\n");
-    match value {
-        Ok(value) => {
-            if let Some(newline) = value.find('\n') {
-                format_to!(s, "value of literal (truncated up to newline): {}", &value[..newline])
-            } else {
-                format_to!(s, "value of literal: {value}")
-            }
-        }
-        Err(error) => format_to!(s, "invalid literal: {error}"),
-    }
-    Some(s.into())
-}
-
 fn render_notable_trait_comment(
     db: &RootDatabase,
     notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
diff --git a/crates/ide/src/syntax_highlighting/escape.rs b/crates/ide/src/syntax_highlighting/escape.rs
index 0439e509d2..5913ca5e45 100644
--- a/crates/ide/src/syntax_highlighting/escape.rs
+++ b/crates/ide/src/syntax_highlighting/escape.rs
@@ -25,7 +25,7 @@ pub(super) fn highlight_escape_string<T: IsString>(
 }
 
 pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) {
-    if char.value().is_err() {
+    if char.value().is_none() {
         // We do not emit invalid escapes highlighting here. The lexer would likely be in a bad
         // state and this token contains junks, since `'` is not a reliable delimiter (consider
         // lifetimes). Nonetheless, parser errors should already be emitted.
@@ -48,7 +48,7 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
 }
 
 pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) {
-    if byte.value().is_err() {
+    if byte.value().is_none() {
         // See `highlight_escape_char` for why no error highlighting here.
         return;
     }
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 10d867fe2b..6bf13ffd06 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -30,7 +30,7 @@ pub(super) fn ra_fixture(
     if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) {
         return None;
     }
-    let value = literal.value().ok()?;
+    let value = literal.value()?;
 
     if let Some(range) = literal.open_quote_text_range() {
         hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index dc8ea51039..76940ab57a 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -361,8 +361,8 @@ fn load_crate_graph(
         }
     }
     let changes = vfs.take_changes();
-    for file in changes {
-        if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
+    for (_, file) in changes {
+        if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change {
             if let Ok(text) = String::from_utf8(v) {
                 analysis_change.change_file(file.file_id, Some(text))
             }
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 34b3e49314..3d8e763721 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -1,8 +1,7 @@
 [package]
 name = "rust-analyzer"
 version = "0.0.0"
-homepage = "https://rust-analyzer.github.io/"
-repository = "https://github.com/rust-analyzer/rust-analyzer"
+homepage = "https://github.com/rust-analyzer/rust-analyzer"
 description = "A language server for the Rust programming language"
 documentation = "https://rust-analyzer.github.io/manual.html"
 autobins = false
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 5208aa9bf0..a1eea8839e 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -986,7 +986,6 @@ impl flags::AnalysisStats {
                     prefer_no_std: false,
                     prefer_prelude: true,
                     style_lints: false,
-                    term_search_fuel: 400,
                 },
                 ide::AssistResolveStrategy::All,
                 file_id,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6c332ae1cb..e16595c992 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -454,9 +454,6 @@ config_data! {
     /// Local configurations can be overridden for every crate by placing a `rust-analyzer.toml` on crate root.
     /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle.
     local: struct LocalDefaultConfigData <- LocalConfigInput ->  {
-        /// Term search fuel in "units of work" for assists (Defaults to 400).
-        assist_termSearch_fuel: usize = 400,
-
         /// Toggles the additional completions that automatically add imports when completed.
         /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
         completion_autoimport_enable: bool       = true,
@@ -518,8 +515,6 @@ config_data! {
         }"#).unwrap(),
         /// Whether to enable term search based snippets like `Some(foo.bar().baz())`.
         completion_termSearch_enable: bool = false,
-        /// Term search fuel in "units of work" for autocompletion (Defaults to 200).
-        completion_termSearch_fuel: usize = 200,
 
         /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
         highlightRelated_breakPoints_enable: bool = true,
@@ -1020,7 +1015,6 @@ impl Config {
             prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
             assist_emit_must_use: self.assist_emitMustUse().to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
-            term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
         }
     }
 
@@ -1054,7 +1048,6 @@ impl Config {
             snippets: self.snippets.clone().to_vec(),
             limit: self.completion_limit(source_root).to_owned(),
             enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
-            term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
         }
     }
@@ -1074,7 +1067,6 @@ impl Config {
             prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
             style_lints: self.diagnostics_styleLints_enable().to_owned(),
-            term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
         }
     }
     pub fn expand_proc_attr_macros(&self) -> bool {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index f5b3ef5103..f64e66183d 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -3,7 +3,7 @@
 //!
 //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
 
-use std::{collections::hash_map::Entry, time::Instant};
+use std::time::Instant;
 
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
@@ -25,7 +25,7 @@ use project_model::{
 use rustc_hash::{FxHashMap, FxHashSet};
 use tracing::{span, Level};
 use triomphe::Arc;
-use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
+use vfs::{AnchoredPathBuf, Vfs};
 
 use crate::{
     config::{Config, ConfigError},
@@ -254,7 +254,6 @@ impl GlobalState {
 
     pub(crate) fn process_changes(&mut self) -> bool {
         let _p = span!(Level::INFO, "GlobalState::process_changes").entered();
-        let mut file_changes = FxHashMap::<_, ChangedFile>::default();
         let (change, modified_rust_files, workspace_structure_change) = {
             let mut change = ChangeWithProcMacros::new();
             let mut guard = self.vfs.write();
@@ -266,44 +265,13 @@ impl GlobalState {
             // downgrade to read lock to allow more readers while we are normalizing text
             let guard = RwLockWriteGuard::downgrade_to_upgradable(guard);
             let vfs: &Vfs = &guard.0;
-            // We need to fix up the changed events a bit. If we have a create or modify for a file
-            // id that is followed by a delete we actually skip observing the file text from the
-            // earlier event, to avoid problems later on.
-            for changed_file in changed_files {
-                use vfs::Change::*;
-                match file_changes.entry(changed_file.file_id) {
-                    Entry::Occupied(mut o) => {
-                        let change = o.get_mut();
-                        match (&mut change.change, changed_file.change) {
-                            // latter `Delete` wins
-                            (change, Delete) => *change = Delete,
-                            // merge `Create` with `Create` or `Modify`
-                            (Create(prev), Create(new) | Modify(new)) => *prev = new,
-                            // collapse identical `Modify`es
-                            (Modify(prev), Modify(new)) => *prev = new,
-                            // equivalent to `Modify`
-                            (change @ Delete, Create(new)) => {
-                                *change = Modify(new);
-                            }
-                            // shouldn't occur, but collapse into `Create`
-                            (change @ Delete, Modify(new)) => {
-                                stdx::never!();
-                                *change = Create(new);
-                            }
-                            // shouldn't occur, but keep the Create
-                            (prev @ Modify(_), new @ Create(_)) => *prev = new,
-                        }
-                    }
-                    Entry::Vacant(v) => _ = v.insert(changed_file),
-                }
-            }
 
             let mut workspace_structure_change = None;
             // A file was added or deleted
             let mut has_structure_changes = false;
             let mut bytes = vec![];
             let mut modified_rust_files = vec![];
-            for file in file_changes.into_values() {
+            for file in changed_files.into_values() {
                 let vfs_path = vfs.file_path(file.file_id);
                 if let Some(path) = vfs_path.as_path() {
                     has_structure_changes |= file.is_created_or_deleted();
@@ -326,16 +294,17 @@ impl GlobalState {
                     self.diagnostics.clear_native_for(file.file_id);
                 }
 
-                let text = if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
-                    String::from_utf8(v).ok().map(|text| {
-                        // FIXME: Consider doing normalization in the `vfs` instead? That allows
-                        // getting rid of some locking
-                        let (text, line_endings) = LineEndings::normalize(text);
-                        (text, line_endings)
-                    })
-                } else {
-                    None
-                };
+                let text =
+                    if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change {
+                        String::from_utf8(v).ok().map(|text| {
+                            // FIXME: Consider doing normalization in the `vfs` instead? That allows
+                            // getting rid of some locking
+                            let (text, line_endings) = LineEndings::normalize(text);
+                            (text, line_endings)
+                        })
+                    } else {
+                        None
+                    };
                 // delay `line_endings_map` changes until we are done normalizing the text
                 // this allows delaying the re-acquisition of the write lock
                 bytes.push((file.file_id, text));
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index cc83d6246b..7b385ca9d9 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -153,7 +153,6 @@ fn integrated_completion_benchmark() {
             prefer_no_std: false,
             prefer_prelude: true,
             limit: None,
-            term_search_fuel: 200,
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -198,7 +197,6 @@ fn integrated_completion_benchmark() {
             prefer_no_std: false,
             prefer_prelude: true,
             limit: None,
-            term_search_fuel: 200,
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -241,7 +239,6 @@ fn integrated_completion_benchmark() {
             prefer_no_std: false,
             prefer_prelude: true,
             limit: None,
-            term_search_fuel: 200,
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -298,7 +295,6 @@ fn integrated_diagnostics_benchmark() {
         },
         prefer_no_std: false,
         prefer_prelude: false,
-        term_search_fuel: 400,
     };
     host.analysis()
         .diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 0504ca50b8..54f10df42a 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -22,6 +22,10 @@ pub fn is_ci() -> bool {
     option_env!("CI").is_some()
 }
 
+pub fn hash_once<Hasher: std::hash::Hasher + Default>(thing: impl std::hash::Hash) -> u64 {
+    std::hash::BuildHasher::hash_one(&std::hash::BuildHasherDefault::<Hasher>::default(), thing)
+}
+
 #[must_use]
 #[allow(clippy::print_stderr)]
 pub fn timeit(label: &'static str) -> impl Drop {
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 16599881d6..7cd1f1550b 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -6,7 +6,7 @@ use std::{
 };
 
 use rustc_lexer::unescape::{
-    unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
+    unescape_byte, unescape_char, unescape_mixed, unescape_unicode, MixedUnit, Mode,
 };
 
 use crate::{
@@ -180,7 +180,10 @@ pub trait IsString: AstToken {
     fn close_quote_text_range(&self) -> Option<TextRange> {
         self.quote_offsets().map(|it| it.quotes.1)
     }
-    fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
+    fn escaped_char_ranges(
+        &self,
+        cb: &mut dyn FnMut(TextRange, Result<char, rustc_lexer::unescape::EscapeError>),
+    ) {
         let text_range_no_quotes = match self.text_range_between_quotes() {
             Some(it) => it,
             None => return,
@@ -209,17 +212,20 @@ impl IsString for ast::String {
 }
 
 impl ast::String {
-    pub fn value(&self) -> Result<Cow<'_, str>, EscapeError> {
-        let text = self.text();
-        let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
-        let text = &text[text_range - self.syntax().text_range().start()];
+    pub fn value(&self) -> Option<Cow<'_, str>> {
         if self.is_raw() {
-            return Ok(Cow::Borrowed(text));
+            let text = self.text();
+            let text =
+                &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+            return Some(Cow::Borrowed(text));
         }
 
+        let text = self.text();
+        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+
         let mut buf = String::new();
         let mut prev_end = 0;
-        let mut has_error = None;
+        let mut has_error = false;
         unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match (
             unescaped_char,
             buf.capacity() == 0,
@@ -233,13 +239,13 @@ impl ast::String {
                 buf.push_str(&text[..prev_end]);
                 buf.push(c);
             }
-            (Err(e), _) => has_error = Some(e),
+            (Err(_), _) => has_error = true,
         });
 
         match (has_error, buf.capacity() == 0) {
-            (Some(e), _) => Err(e),
-            (None, true) => Ok(Cow::Borrowed(text)),
-            (None, false) => Ok(Cow::Owned(buf)),
+            (true, _) => None,
+            (false, true) => Some(Cow::Borrowed(text)),
+            (false, false) => Some(Cow::Owned(buf)),
         }
     }
 }
@@ -250,17 +256,20 @@ impl IsString for ast::ByteString {
 }
 
 impl ast::ByteString {
-    pub fn value(&self) -> Result<Cow<'_, [u8]>, EscapeError> {
-        let text = self.text();
-        let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
-        let text = &text[text_range - self.syntax().text_range().start()];
+    pub fn value(&self) -> Option<Cow<'_, [u8]>> {
         if self.is_raw() {
-            return Ok(Cow::Borrowed(text.as_bytes()));
+            let text = self.text();
+            let text =
+                &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+            return Some(Cow::Borrowed(text.as_bytes()));
         }
 
+        let text = self.text();
+        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+
         let mut buf: Vec<u8> = Vec::new();
         let mut prev_end = 0;
-        let mut has_error = None;
+        let mut has_error = false;
         unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match (
             unescaped_char,
             buf.capacity() == 0,
@@ -274,13 +283,13 @@ impl ast::ByteString {
                 buf.extend_from_slice(text[..prev_end].as_bytes());
                 buf.push(c as u8);
             }
-            (Err(e), _) => has_error = Some(e),
+            (Err(_), _) => has_error = true,
         });
 
         match (has_error, buf.capacity() == 0) {
-            (Some(e), _) => Err(e),
-            (None, true) => Ok(Cow::Borrowed(text.as_bytes())),
-            (None, false) => Ok(Cow::Owned(buf)),
+            (true, _) => None,
+            (false, true) => Some(Cow::Borrowed(text.as_bytes())),
+            (false, false) => Some(Cow::Owned(buf)),
         }
     }
 }
@@ -289,7 +298,10 @@ impl IsString for ast::CString {
     const RAW_PREFIX: &'static str = "cr";
     const MODE: Mode = Mode::CStr;
 
-    fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
+    fn escaped_char_ranges(
+        &self,
+        cb: &mut dyn FnMut(TextRange, Result<char, rustc_lexer::unescape::EscapeError>),
+    ) {
         let text_range_no_quotes = match self.text_range_between_quotes() {
             Some(it) => it,
             None => return,
@@ -310,17 +322,20 @@ impl IsString for ast::CString {
 }
 
 impl ast::CString {
-    pub fn value(&self) -> Result<Cow<'_, [u8]>, EscapeError> {
-        let text = self.text();
-        let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
-        let text = &text[text_range - self.syntax().text_range().start()];
+    pub fn value(&self) -> Option<Cow<'_, [u8]>> {
         if self.is_raw() {
-            return Ok(Cow::Borrowed(text.as_bytes()));
+            let text = self.text();
+            let text =
+                &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+            return Some(Cow::Borrowed(text.as_bytes()));
         }
 
+        let text = self.text();
+        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+
         let mut buf = Vec::new();
         let mut prev_end = 0;
-        let mut has_error = None;
+        let mut has_error = false;
         let extend_unit = |buf: &mut Vec<u8>, unit: MixedUnit| match unit {
             MixedUnit::Char(c) => buf.extend(c.encode_utf8(&mut [0; 4]).as_bytes()),
             MixedUnit::HighByte(b) => buf.push(b),
@@ -338,13 +353,13 @@ impl ast::CString {
                 buf.extend(text[..prev_end].as_bytes());
                 extend_unit(&mut buf, u);
             }
-            (Err(e), _) => has_error = Some(e),
+            (Err(_), _) => has_error = true,
         });
 
         match (has_error, buf.capacity() == 0) {
-            (Some(e), _) => Err(e),
-            (None, true) => Ok(Cow::Borrowed(text.as_bytes())),
-            (None, false) => Ok(Cow::Owned(buf)),
+            (true, _) => None,
+            (false, true) => Some(Cow::Borrowed(text.as_bytes())),
+            (false, false) => Some(Cow::Owned(buf)),
         }
     }
 }
@@ -463,34 +478,34 @@ impl Radix {
 }
 
 impl ast::Char {
-    pub fn value(&self) -> Result<char, EscapeError> {
+    pub fn value(&self) -> Option<char> {
         let mut text = self.text();
         if text.starts_with('\'') {
             text = &text[1..];
         } else {
-            return Err(EscapeError::ZeroChars);
+            return None;
         }
         if text.ends_with('\'') {
             text = &text[0..text.len() - 1];
         }
 
-        unescape_char(text)
+        unescape_char(text).ok()
     }
 }
 
 impl ast::Byte {
-    pub fn value(&self) -> Result<u8, EscapeError> {
+    pub fn value(&self) -> Option<u8> {
         let mut text = self.text();
         if text.starts_with("b\'") {
             text = &text[2..];
         } else {
-            return Err(EscapeError::ZeroChars);
+            return None;
         }
         if text.ends_with('\'') {
             text = &text[0..text.len() - 1];
         }
 
-        unescape_byte(text)
+        unescape_byte(text).ok()
     }
 }
 
@@ -544,10 +559,7 @@ mod tests {
 
     fn check_string_value<'a>(lit: &str, expected: impl Into<Option<&'a str>>) {
         assert_eq!(
-            ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) }
-                .value()
-                .as_deref()
-                .ok(),
+            ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) }.value().as_deref(),
             expected.into()
         );
     }
@@ -572,8 +584,7 @@ bcde", "abcde",
         assert_eq!(
             ast::ByteString { syntax: make::tokens::literal(&format!("b\"{lit}\"")) }
                 .value()
-                .as_deref()
-                .ok(),
+                .as_deref(),
             expected.into().map(|value| &value[..])
         );
     }
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
index cb3e058355..b07e97cd6c 100644
--- a/crates/vfs/src/lib.rs
+++ b/crates/vfs/src/lib.rs
@@ -46,7 +46,7 @@ pub mod loader;
 mod path_interner;
 mod vfs_path;
 
-use std::{fmt, mem};
+use std::{fmt, hash::BuildHasherDefault, mem};
 
 use crate::path_interner::PathInterner;
 
@@ -54,8 +54,11 @@ pub use crate::{
     anchored_path::{AnchoredPath, AnchoredPathBuf},
     vfs_path::VfsPath,
 };
+use indexmap::{map::Entry, IndexMap};
 pub use paths::{AbsPath, AbsPathBuf};
 
+use rustc_hash::FxHasher;
+use stdx::hash_once;
 use tracing::{span, Level};
 
 /// Handle to a file in [`Vfs`]
@@ -93,20 +96,13 @@ impl nohash_hasher::IsEnabled for FileId {}
 pub struct Vfs {
     interner: PathInterner,
     data: Vec<FileState>,
-    // FIXME: This should be a HashMap<FileId, ChangeFile>
-    // right now we do a nasty deduplication in GlobalState::process_changes that would be a lot
-    // easier to handle here on insertion.
-    changes: Vec<ChangedFile>,
-    // The above FIXME would then also get rid of this probably
-    created_this_cycle: Vec<FileId>,
+    changes: IndexMap<FileId, ChangedFile, BuildHasherDefault<FxHasher>>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
 pub enum FileState {
-    /// The file has been created this cycle.
-    Created,
-    /// The file exists.
-    Exists,
+    /// The file exists with the given content hash.
+    Exists(u64),
     /// The file is deleted.
     Deleted,
 }
@@ -129,23 +125,23 @@ impl ChangedFile {
     /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
     /// [`Delete`](Change::Delete).
     pub fn is_created_or_deleted(&self) -> bool {
-        matches!(self.change, Change::Create(_) | Change::Delete)
+        matches!(self.change, Change::Create(_, _) | Change::Delete)
     }
 
     /// Returns `true` if the change is [`Create`](ChangeKind::Create).
     pub fn is_created(&self) -> bool {
-        matches!(self.change, Change::Create(_))
+        matches!(self.change, Change::Create(_, _))
     }
 
     /// Returns `true` if the change is [`Modify`](ChangeKind::Modify).
     pub fn is_modified(&self) -> bool {
-        matches!(self.change, Change::Modify(_))
+        matches!(self.change, Change::Modify(_, _))
     }
 
     pub fn kind(&self) -> ChangeKind {
         match self.change {
-            Change::Create(_) => ChangeKind::Create,
-            Change::Modify(_) => ChangeKind::Modify,
+            Change::Create(_, _) => ChangeKind::Create,
+            Change::Modify(_, _) => ChangeKind::Modify,
             Change::Delete => ChangeKind::Delete,
         }
     }
@@ -155,9 +151,9 @@ impl ChangedFile {
 #[derive(Eq, PartialEq, Debug)]
 pub enum Change {
     /// The file was (re-)created
-    Create(Vec<u8>),
+    Create(Vec<u8>, u64),
     /// The file was modified
-    Modify(Vec<u8>),
+    Modify(Vec<u8>, u64),
     /// The file was deleted
     Delete,
 }
@@ -176,9 +172,7 @@ pub enum ChangeKind {
 impl Vfs {
     /// Id of the given path if it exists in the `Vfs` and is not deleted.
     pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
-        self.interner
-            .get(path)
-            .filter(|&it| matches!(self.get(it), FileState::Exists | FileState::Created))
+        self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists(_)))
     }
 
     /// File path corresponding to the given `file_id`.
@@ -196,9 +190,7 @@ impl Vfs {
     pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
         (0..self.data.len())
             .map(|it| FileId(it as u32))
-            .filter(move |&file_id| {
-                matches!(self.get(file_id), FileState::Exists | FileState::Created)
-            })
+            .filter(move |&file_id| matches!(self.get(file_id), FileState::Exists(_)))
             .map(move |file_id| {
                 let path = self.interner.lookup(file_id);
                 (file_id, path)
@@ -217,41 +209,74 @@ impl Vfs {
         let state = self.get(file_id);
         let change_kind = match (state, contents) {
             (FileState::Deleted, None) => return false,
-            (FileState::Deleted, Some(v)) => Change::Create(v),
-            (FileState::Exists | FileState::Created, None) => Change::Delete,
-            (FileState::Exists | FileState::Created, Some(v)) => Change::Modify(v),
-        };
-        self.data[file_id.0 as usize] = match change_kind {
-            Change::Create(_) => {
-                self.created_this_cycle.push(file_id);
-                FileState::Created
+            (FileState::Deleted, Some(v)) => {
+                let hash = hash_once::<FxHasher>(&*v);
+                Change::Create(v, hash)
+            }
+            (FileState::Exists(_), None) => Change::Delete,
+            (FileState::Exists(hash), Some(v)) => {
+                let new_hash = hash_once::<FxHasher>(&*v);
+                if new_hash == hash {
+                    return false;
+                }
+                Change::Modify(v, new_hash)
             }
-            // If the file got created this cycle, make sure we keep it that way even
-            // if a modify comes in
-            Change::Modify(_) if matches!(state, FileState::Created) => FileState::Created,
-            Change::Modify(_) => FileState::Exists,
-            Change::Delete => FileState::Deleted,
         };
+
+        let mut set_data = |change_kind| {
+            self.data[file_id.0 as usize] = match change_kind {
+                &Change::Create(_, hash) | &Change::Modify(_, hash) => FileState::Exists(hash),
+                Change::Delete => FileState::Deleted,
+            };
+        };
+
         let changed_file = ChangedFile { file_id, change: change_kind };
-        self.changes.push(changed_file);
+        match self.changes.entry(file_id) {
+            // two changes to the same file in one cycle, merge them appropriately
+            Entry::Occupied(mut o) => {
+                use Change::*;
+
+                match (&mut o.get_mut().change, changed_file.change) {
+                    // newer `Delete` wins
+                    (change, Delete) => *change = Delete,
+                    // merge `Create` with `Create` or `Modify`
+                    (Create(prev, old_hash), Create(new, new_hash) | Modify(new, new_hash)) => {
+                        *prev = new;
+                        *old_hash = new_hash;
+                    }
+                    // collapse identical `Modify`es
+                    (Modify(prev, old_hash), Modify(new, new_hash)) => {
+                        *prev = new;
+                        *old_hash = new_hash;
+                    }
+                    // equivalent to `Modify`
+                    (change @ Delete, Create(new, new_hash)) => {
+                        *change = Modify(new, new_hash);
+                    }
+                    // shouldn't occur, but collapse into `Create`
+                    (change @ Delete, Modify(new, new_hash)) => {
+                        stdx::never!();
+                        *change = Create(new, new_hash);
+                    }
+                    // shouldn't occur, but keep the Create
+                    (prev @ Modify(_, _), new @ Create(_, _)) => *prev = new,
+                }
+                set_data(&o.get().change);
+            }
+            Entry::Vacant(v) => set_data(&v.insert(changed_file).change),
+        };
+
         true
     }
 
     /// Drain and returns all the changes in the `Vfs`.
-    pub fn take_changes(&mut self) -> Vec<ChangedFile> {
-        let _p = span!(Level::INFO, "Vfs::take_changes").entered();
-        for file_id in self.created_this_cycle.drain(..) {
-            if self.data[file_id.0 as usize] == FileState::Created {
-                // downgrade the file from `Created` to `Exists` as the cycle is done
-                self.data[file_id.0 as usize] = FileState::Exists;
-            }
-        }
+    pub fn take_changes(&mut self) -> IndexMap<FileId, ChangedFile, BuildHasherDefault<FxHasher>> {
         mem::take(&mut self.changes)
     }
 
     /// Provides a panic-less way to verify file_id validity.
     pub fn exists(&self, file_id: FileId) -> bool {
-        matches!(self.get(file_id), FileState::Exists | FileState::Created)
+        matches!(self.get(file_id), FileState::Exists(_))
     }
 
     /// Returns the id associated with `path`
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 8993a46d2b..e9d60063c6 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -9,11 +9,6 @@ for enum variants.
 --
 Placeholder expression to use for missing expressions in assists.
 --
-[[rust-analyzer.assist.termSearch.fuel]]rust-analyzer.assist.termSearch.fuel (default: `400`)::
-+
---
-Term search fuel in "units of work" for assists (Defaults to 400).
---
 [[rust-analyzer.cachePriming.enable]]rust-analyzer.cachePriming.enable (default: `true`)::
 +
 --
@@ -378,11 +373,6 @@ Custom completion snippets.
 --
 Whether to enable term search based snippets like `Some(foo.bar().baz())`.
 --
-[[rust-analyzer.completion.termSearch.fuel]]rust-analyzer.completion.termSearch.fuel (default: `200`)::
-+
---
-Term search fuel in "units of work" for autocompletion (Defaults to 200).
---
 [[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 6e4fedd992..c4bbb7932e 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -534,12 +534,6 @@
                         "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
                     ]
                 },
-                "rust-analyzer.assist.termSearch.fuel": {
-                    "markdownDescription": "Term search fuel in \"units of work\" for assists (Defaults to 400).",
-                    "default": 400,
-                    "type": "integer",
-                    "minimum": 0
-                },
                 "rust-analyzer.cachePriming.enable": {
                     "markdownDescription": "Warm up caches on project load.",
                     "default": true,
@@ -936,12 +930,6 @@
                     "default": false,
                     "type": "boolean"
                 },
-                "rust-analyzer.completion.termSearch.fuel": {
-                    "markdownDescription": "Term search fuel in \"units of work\" for autocompletion (Defaults to 200).",
-                    "default": 200,
-                    "type": "integer",
-                    "minimum": 0
-                },
                 "rust-analyzer.diagnostics.disabled": {
                     "markdownDescription": "List of rust-analyzer diagnostics to disable.",
                     "default": [],


Test patch:
diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs
index 3b6c951251..32d6984102 100644
--- a/crates/ide-assists/src/tests.rs
+++ b/crates/ide-assists/src/tests.rs
@@ -31,7 +31,6 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
     prefer_no_std: false,
     prefer_prelude: true,
     assist_emit_must_use: false,
-    term_search_fuel: 400,
 };
 
 pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
@@ -47,7 +46,6 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
     prefer_no_std: false,
     prefer_prelude: true,
     assist_emit_must_use: false,
-    term_search_fuel: 400,
 };
 
 pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig {
@@ -63,7 +61,6 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig {
     prefer_no_std: false,
     prefer_prelude: true,
     assist_emit_must_use: false,
-    term_search_fuel: 400,
 };
 
 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 70e0aa4e9a..1f032c7df4 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -80,7 +80,6 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
     },
     snippets: Vec::new(),
     limit: None,
-    term_search_fuel: 200,
 };
 
 pub(crate) fn completion_list(ra_fixture: &str) -> String {
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 76d86f8ede..f7af795004 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -7790,12 +7790,9 @@ fn main() {
 "#,
         expect![[r#"
             *"🦀\u{1f980}\\\x41"*
-            ```rust
-            &str
+            ```text
+            🦀🦀\A
             ```
-            ___
-
-            value of literal: 🦀🦀\A
         "#]],
     );
     check(
@@ -7806,34 +7803,9 @@ fn main() {
 "#,
         expect![[r#"
             *r"🦀\u{1f980}\\\x41"*
-            ```rust
-            &str
-            ```
-            ___
-
-            value of literal: 🦀\u{1f980}\\\x41
-        "#]],
-    );
-    check(
-        r#"
-fn main() {
-    $0r"🦀\u{1f980}\\\x41
-
-
-fsdghs";
-}
-"#,
-        expect![[r#"
-            *r"🦀\u{1f980}\\\x41
-
-
-            fsdghs"*
-            ```rust
-            &str
+            ```text
+            🦀\u{1f980}\\\x41
             ```
-            ___
-
-            value of literal (truncated up to newline): 🦀\u{1f980}\\\x41
         "#]],
     );
 }
@@ -7848,12 +7820,9 @@ fn main() {
 "#,
         expect![[r#"
             *c"🦀\u{1f980}\\\x41"*
-            ```rust
-            &{unknown}
+            ```text
+            🦀🦀\A
             ```
-            ___
-
-            value of literal: 🦀🦀\A
         "#]],
     );
 }
@@ -7868,12 +7837,9 @@ fn main() {
 "#,
         expect![[r#"
             *b"\xF0\x9F\xA6\x80\\"*
-            ```rust
-            &[u8; 5]
+            ```text
+            [240, 159, 166, 128, 92]
             ```
-            ___
-
-            value of literal: [240, 159, 166, 128, 92]
         "#]],
     );
     check(
@@ -7884,12 +7850,9 @@ fn main() {
 "#,
         expect![[r#"
             *br"\xF0\x9F\xA6\x80\\"*
-            ```rust
-            &[u8; 18]
+            ```text
+            [92, 120, 70, 48, 92, 120, 57, 70, 92, 120, 65, 54, 92, 120, 56, 48, 92, 92]
             ```
-            ___
-
-            value of literal: [92, 120, 70, 48, 92, 120, 57, 70, 92, 120, 65, 54, 92, 120, 56, 48, 92, 92]
         "#]],
     );
 }
@@ -7904,12 +7867,9 @@ fn main() {
 "#,
         expect![[r#"
             *b'\xF0'*
-            ```rust
-            u8
+            ```text
+            0xF0
             ```
-            ___
-
-            value of literal: 0xF0
         "#]],
     );
     check(
@@ -7920,12 +7880,9 @@ fn main() {
 "#,
         expect![[r#"
             *b'\\'*
-            ```rust
-            u8
+            ```text
+            0x5C
             ```
-            ___
-
-            value of literal: 0x5C
         "#]],
     );
 }
@@ -7940,12 +7897,7 @@ fn main() {
 "#,
         expect![[r#"
             *'\x41'*
-            ```rust
-            char
-            ```
-            ___
 
-            value of literal: A
         "#]],
     );
     check(
@@ -7956,12 +7908,7 @@ fn main() {
 "#,
         expect![[r#"
             *'\\'*
-            ```rust
-            char
-            ```
-            ___
 
-            value of literal: \
         "#]],
     );
     check(
@@ -7972,12 +7919,7 @@ fn main() {
 "#,
         expect![[r#"
             *'\u{1f980}'*
-            ```rust
-            char
-            ```
-            ___
 
-            value of literal: 🦀
         "#]],
     );
 }
@@ -7992,12 +7934,9 @@ fn main() {
 "#,
         expect![[r#"
             *1.0*
-            ```rust
-            f64
+            ```text
+            1 (bits: 0x3FF0000000000000)
             ```
-            ___
-
-            value of literal: 1 (bits: 0x3FF0000000000000)
         "#]],
     );
     check(
@@ -8008,12 +7947,9 @@ fn main() {
 "#,
         expect![[r#"
             *1.0f32*
-            ```rust
-            f32
+            ```text
+            1 (bits: 0x3F800000)
             ```
-            ___
-
-            value of literal: 1 (bits: 0x3F800000)
         "#]],
     );
     check(
@@ -8024,12 +7960,9 @@ fn main() {
 "#,
         expect![[r#"
             *134e12*
-            ```rust
-            f64
+            ```text
+            134000000000000 (bits: 0x42DE77D399980000)
             ```
-            ___
-
-            value of literal: 134000000000000 (bits: 0x42DE77D399980000)
         "#]],
     );
     check(
@@ -8040,12 +7973,9 @@ fn main() {
 "#,
         expect![[r#"
             *1523527134274733643531312.0*
-            ```rust
-            f64
+            ```text
+            1523527134274733600000000 (bits: 0x44F429E9249F629B)
             ```
-            ___
-
-            value of literal: 1523527134274733600000000 (bits: 0x44F429E9249F629B)
         "#]],
     );
     check(
@@ -8056,12 +7986,9 @@ fn main() {
 "#,
         expect![[r#"
             *0.1ea123*
-            ```rust
-            f64
+            ```text
+            invalid float literal
             ```
-            ___
-
-            invalid literal: invalid float literal
         "#]],
     );
 }
@@ -8076,12 +8003,9 @@ fn main() {
 "#,
         expect![[r#"
             *34325236457856836345234*
-            ```rust
-            i32
+            ```text
+            34325236457856836345234 (0x744C659178614489D92|0b111010001001100011001011001000101111000011000010100010010001001110110010010)
             ```
-            ___
-
-            value of literal: 34325236457856836345234 (0x744C659178614489D92|0b111010001001100011001011001000101111000011000010100010010001001110110010010)
         "#]],
     );
     check(
@@ -8092,12 +8016,9 @@ fn main() {
 "#,
         expect![[r#"
             *134_123424_21*
-            ```rust
-            i32
+            ```text
+            13412342421 (0x31F701A95|0b1100011111011100000001101010010101)
             ```
-            ___
-
-            value of literal: 13412342421 (0x31F701A95|0b1100011111011100000001101010010101)
         "#]],
     );
     check(
@@ -8108,12 +8029,9 @@ fn main() {
 "#,
         expect![[r#"
             *0x12423423*
-            ```rust
-            i32
+            ```text
+            306328611 (0x12423423|0b10010010000100011010000100011)
             ```
-            ___
-
-            value of literal: 306328611 (0x12423423|0b10010010000100011010000100011)
         "#]],
     );
     check(
@@ -8124,12 +8042,9 @@ fn main() {
 "#,
         expect![[r#"
             *0b1111_1111*
-            ```rust
-            i32
+            ```text
+            255 (0xFF|0b11111111)
             ```
-            ___
-
-            value of literal: 255 (0xFF|0b11111111)
         "#]],
     );
     check(
@@ -8140,12 +8055,9 @@ fn main() {
 "#,
         expect![[r#"
             *0o12345*
-            ```rust
-            i32
+            ```text
+            5349 (0x14E5|0b1010011100101)
             ```
-            ___
-
-            value of literal: 5349 (0x14E5|0b1010011100101)
         "#]],
     );
     check(
@@ -8156,12 +8068,9 @@ fn main() {
 "#,
         expect![[r#"
             *0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_F*
-            ```rust
-            i32
+            ```text
+            number too large to fit in target type
             ```
-            ___
-
-            invalid literal: number too large to fit in target type
         "#]],
     );
 }