Repository: rust-analyzer/rust-analyzer
Pull request #8364 (2021-04-06 07:23:56)

Memory usage improvements
These are mostly focused on splitting up enum variants with large size differences between variants by `Box`-ing things up.

In my testing this reduces the memory usage somewhere in the low percentages, even though the measurements are quite noisy.

lnicola: > In my testing this reduces the memory usage somewhere in the low percentages, even though the measurements are quite noisy.

Did you try `rust-analyzer analysis-stats --memory-usage`? It's quite stable on CI: https://rust-analyzer.github.io/metrics/

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e40c12a92e..ca03115951 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1867,6 +1867,7 @@ name = "vfs"
 version = "0.0.0"
 dependencies = [
  "fst",
+ "indexmap",
  "paths",
  "rustc-hash",
 ]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index dfc1d8a0c6..db4ebada4a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1888,10 +1888,9 @@ impl Type {
                     substs.iter(&Interner).filter_map(|a| a.ty(&Interner)).any(go)
                 }
 
-                TyKind::Array(ty, _)
-                | TyKind::Slice(ty)
-                | TyKind::Raw(_, ty)
-                | TyKind::Ref(_, _, ty) => go(ty),
+                TyKind::Array(ty) | TyKind::Slice(ty) | TyKind::Raw(_, ty) | TyKind::Ref(_, ty) => {
+                    go(ty)
+                }
 
                 TyKind::Scalar(_)
                 | TyKind::Str
@@ -2149,10 +2148,7 @@ impl Type {
                     );
                 }
 
-                TyKind::Ref(_, _, ty)
-                | TyKind::Raw(_, ty)
-                | TyKind::Array(ty, _)
-                | TyKind::Slice(ty) => {
+                TyKind::Ref(_, ty) | TyKind::Raw(_, ty) | TyKind::Array(ty) | TyKind::Slice(ty) => {
                     walk_type(db, &type_.derived(ty.clone()), cb);
                 }
 
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 63e89a1f42..1e743e5d5a 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -322,8 +322,10 @@ impl ExprCollector<'_> {
                     Vec::new()
                 };
                 let method_name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
-                let generic_args =
-                    e.generic_arg_list().and_then(|it| GenericArgs::from_ast(&self.ctx(), it));
+                let generic_args = e
+                    .generic_arg_list()
+                    .and_then(|it| GenericArgs::from_ast(&self.ctx(), it))
+                    .map(Box::new);
                 self.alloc_expr(
                     Expr::MethodCall { receiver, method_name, args, generic_args },
                     syntax_ptr,
@@ -385,7 +387,7 @@ impl ExprCollector<'_> {
                 self.alloc_expr(Expr::Yield { expr }, syntax_ptr)
             }
             ast::Expr::RecordExpr(e) => {
-                let path = e.path().and_then(|path| self.expander.parse_path(path));
+                let path = e.path().and_then(|path| self.expander.parse_path(path)).map(Box::new);
                 let record_lit = if let Some(nfl) = e.record_expr_field_list() {
                     let fields = nfl
                         .fields()
@@ -430,7 +432,7 @@ impl ExprCollector<'_> {
             }
             ast::Expr::CastExpr(e) => {
                 let expr = self.collect_expr_opt(e.expr());
-                let type_ref = TypeRef::from_ast_opt(&self.ctx(), e.ty());
+                let type_ref = Box::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
                 self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr)
             }
             ast::Expr::RefExpr(e) => {
@@ -469,8 +471,10 @@ impl ExprCollector<'_> {
                         arg_types.push(type_ref);
                     }
                 }
-                let ret_type =
-                    e.ret_type().and_then(|r| r.ty()).map(|it| TypeRef::from_ast(&self.ctx(), it));
+                let ret_type = e
+                    .ret_type()
+                    .and_then(|r| r.ty())
+                    .map(|it| Box::new(TypeRef::from_ast(&self.ctx(), it)));
                 let body = self.collect_expr_opt(e.body());
                 self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr)
             }
@@ -755,7 +759,7 @@ impl ExprCollector<'_> {
                 }
             }
             ast::Pat::TupleStructPat(p) => {
-                let path = p.path().and_then(|path| self.expander.parse_path(path));
+                let path = p.path().and_then(|path| self.expander.parse_path(path)).map(Box::new);
                 let (args, ellipsis) = self.collect_tuple_pat(p.fields());
                 Pat::TupleStruct { path, args, ellipsis }
             }
@@ -765,7 +769,7 @@ impl ExprCollector<'_> {
                 Pat::Ref { pat, mutability }
             }
             ast::Pat::PathPat(p) => {
-                let path = p.path().and_then(|path| self.expander.parse_path(path));
+                let path = p.path().and_then(|path| self.expander.parse_path(path)).map(Box::new);
                 path.map(Pat::Path).unwrap_or(Pat::Missing)
             }
             ast::Pat::OrPat(p) => {
@@ -779,7 +783,7 @@ impl ExprCollector<'_> {
             }
             ast::Pat::WildcardPat(_) => Pat::Wild,
             ast::Pat::RecordPat(p) => {
-                let path = p.path().and_then(|path| self.expander.parse_path(path));
+                let path = p.path().and_then(|path| self.expander.parse_path(path)).map(Box::new);
                 let args: Vec<_> = p
                     .record_pat_field_list()
                     .expect("every struct should have a field list")
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 6c7376fad3..62a28bdba7 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -86,7 +86,7 @@ pub enum Expr {
         receiver: ExprId,
         method_name: Name,
         args: Vec<ExprId>,
-        generic_args: Option<GenericArgs>,
+        generic_args: Option<Box<GenericArgs>>,
     },
     Match {
         expr: ExprId,
@@ -106,7 +106,7 @@ pub enum Expr {
         expr: Option<ExprId>,
     },
     RecordLit {
-        path: Option<Path>,
+        path: Option<Box<Path>>,
         fields: Vec<RecordLitField>,
         spread: Option<ExprId>,
     },
@@ -131,7 +131,7 @@ pub enum Expr {
     },
     Cast {
         expr: ExprId,
-        type_ref: TypeRef,
+        type_ref: Box<TypeRef>,
     },
     Ref {
         expr: ExprId,
@@ -162,7 +162,7 @@ pub enum Expr {
     Lambda {
         args: Vec<PatId>,
         arg_types: Vec<Option<TypeRef>>,
-        ret_type: Option<TypeRef>,
+        ret_type: Option<Box<TypeRef>>,
         body: ExprId,
     },
     Tuple {
@@ -412,13 +412,13 @@ pub enum Pat {
     Wild,
     Tuple { args: Vec<PatId>, ellipsis: Option<usize> },
     Or(Vec<PatId>),
-    Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
+    Record { path: Option<Box<Path>>, args: Vec<RecordFieldPat>, ellipsis: bool },
     Range { start: ExprId, end: ExprId },
     Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
-    Path(Path),
+    Path(Box<Path>),
     Lit(ExprId),
     Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
-    TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
+    TupleStruct { path: Option<Box<Path>>, args: Vec<PatId>, ellipsis: Option<usize> },
     Ref { pat: PatId, mutability: Mutability },
     Box { inner: PatId },
     ConstBlock(ExprId),
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index f9c8328f06..b528ff8bad 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -289,6 +289,12 @@ impl From<Name> for Path {
     }
 }
 
+impl From<Name> for Box<Path> {
+    fn from(name: Name) -> Box<Path> {
+        Box::new(Path::from(name))
+    }
+}
+
 impl From<Name> for ModPath {
     fn from(name: Name) -> ModPath {
         ModPath::from_segments(PathKind::Plain, iter::once(name))
diff --git a/crates/hir_ty/src/db.rs b/crates/hir_ty/src/db.rs
index 326c202404..07510ae02b 100644
--- a/crates/hir_ty/src/db.rs
+++ b/crates/hir_ty/src/db.rs
@@ -88,8 +88,6 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::interned]
     fn intern_lifetime_param_id(&self, param_id: LifetimeParamId) -> InternedLifetimeParamId;
     #[salsa::interned]
-    fn intern_const_param_id(&self, param_id: ConstParamId) -> InternedConstParamId;
-    #[salsa::interned]
     fn intern_impl_trait_id(&self, id: ImplTraitId) -> InternedOpaqueTyId;
     #[salsa::interned]
     fn intern_closure(&self, id: (DefWithBodyId, ExprId)) -> InternedClosureId;
@@ -163,10 +161,6 @@ impl_intern_key!(InternedTypeParamId);
 pub struct InternedLifetimeParamId(salsa::InternId);
 impl_intern_key!(InternedLifetimeParamId);
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct InternedConstParamId(salsa::InternId);
-impl_intern_key!(InternedConstParamId);
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct InternedOpaqueTyId(salsa::InternId);
 impl_intern_key!(InternedOpaqueTyId);
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index d7bf9fdf7e..db278d0db2 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -315,7 +315,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
                 if pat_ty == match_expr_ty
                     || match_expr_ty
                         .as_reference()
-                        .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
+                        .map(|(match_expr_ty, _)| match_expr_ty == pat_ty)
                         .unwrap_or(false)
                 {
                     // If we had a NotUsefulMatchArm diagnostic, we could
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 8fe4ed3fa1..4ef8024d0a 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -5,7 +5,6 @@ use std::{
     fmt::{self, Debug},
 };
 
-use chalk_ir::BoundVar;
 use hir_def::{
     db::DefDatabase,
     find_path,
@@ -19,12 +18,12 @@ use hir_def::{
 use hir_expand::name::Name;
 
 use crate::{
-    const_from_placeholder_idx, db::HirDatabase, from_assoc_type_id, from_foreign_def_id,
-    from_placeholder_idx, lt_from_placeholder_idx, primitive, subst_prefix, to_assoc_type_id,
-    traits::chalk::from_chalk, utils::generics, AdtId, AliasEq, AliasTy, CallableDefId,
-    CallableSig, Const, ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime,
-    LifetimeData, LifetimeOutlives, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt,
-    QuantifiedWhereClause, Scalar, TraitRef, Ty, TyExt, TyKind, WhereClause,
+    db::HirDatabase, from_assoc_type_id, from_foreign_def_id, from_placeholder_idx,
+    lt_from_placeholder_idx, primitive, subst_prefix, to_assoc_type_id, traits::chalk::from_chalk,
+    utils::generics, AdtId, AliasEq, AliasTy, CallableDefId, CallableSig, DomainGoal, GenericArg,
+    ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability, OpaqueTy,
+    ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, TraitRef, Ty, TyExt, TyKind,
+    WhereClause,
 };
 
 pub struct HirFormatter<'a> {
@@ -291,29 +290,6 @@ impl HirDisplay for GenericArg {
     }
 }
 
-impl HirDisplay for Const {
-    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
-        let data = self.interned();
-        match data.value {
-            ConstValue::BoundVar(idx) => idx.hir_fmt(f),
-            ConstValue::InferenceVar(..) => write!(f, "_"),
-            ConstValue::Placeholder(idx) => {
-                let id = const_from_placeholder_idx(f.db, idx);
-                let generics = generics(f.db.upcast(), id.parent);
-                let param_data = &generics.params.consts[id.local_id];
-                write!(f, "{}", param_data.name)
-            }
-            ConstValue::Concrete(_) => write!(f, "_"),
-        }
-    }
-}
-
-impl HirDisplay for BoundVar {
-    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
-        write!(f, "?{}.{}", self.debruijn.depth(), self.index)
-    }
-}
-
 impl HirDisplay for Ty {
     fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
         if f.should_truncate() {
@@ -333,14 +309,12 @@ impl HirDisplay for Ty {
                 t.hir_fmt(f)?;
                 write!(f, "]")?;
             }
-            TyKind::Array(t, c) => {
+            TyKind::Array(t) => {
                 write!(f, "[")?;
                 t.hir_fmt(f)?;
-                write!(f, "; ")?;
-                c.hir_fmt(f)?;
-                write!(f, "]")?;
+                write!(f, "; _]")?;
             }
-            TyKind::Raw(m, t) | TyKind::Ref(m, _, t) => {
+            TyKind::Raw(m, t) | TyKind::Ref(m, t) => {
                 let ty_display =
                     t.into_displayable(f.db, f.max_size, f.omit_verbose_types, f.display_target);
 
@@ -643,7 +617,7 @@ impl HirDisplay for Ty {
                     }
                 }
             }
-            TyKind::BoundVar(idx) => idx.hir_fmt(f)?,
+            TyKind::BoundVar(idx) => write!(f, "?{}.{}", idx.debruijn.depth(), idx.index)?,
             TyKind::Dyn(dyn_ty) => {
                 write_bounds_like_dyn_trait_with_prefix(
                     "dyn",
@@ -876,7 +850,7 @@ impl HirDisplay for Lifetime {
 impl HirDisplay for LifetimeData {
     fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
         match self {
-            LifetimeData::BoundVar(idx) => idx.hir_fmt(f),
+            LifetimeData::BoundVar(idx) => write!(f, "?{}.{}", idx.debruijn.depth(), idx.index),
             LifetimeData::InferenceVar(_) => write!(f, "_"),
             LifetimeData::Placeholder(idx) => {
                 let id = lt_from_placeholder_idx(f.db, *idx);
diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs
index d6c48870ad..32c273afcd 100644
--- a/crates/hir_ty/src/infer/coerce.rs
+++ b/crates/hir_ty/src/infer/coerce.rs
@@ -81,7 +81,7 @@ impl<'a> InferenceContext<'a> {
             // `&T` -> `*const T`
             // `&mut T` -> `*mut T`/`*const T`
             (TyKind::Ref(.., substs), &TyKind::Raw(m2 @ Mutability::Not, ..))
-            | (TyKind::Ref(Mutability::Mut, _, substs), &TyKind::Raw(m2, ..)) => {
+            | (TyKind::Ref(Mutability::Mut, substs), &TyKind::Raw(m2, ..)) => {
                 from_ty = TyKind::Raw(m2, substs.clone()).intern(&Interner);
             }
 
@@ -111,9 +111,7 @@ impl<'a> InferenceContext<'a> {
         // Auto Deref if cannot coerce
         match (from_ty.kind(&Interner), to_ty.kind(&Interner)) {
             // FIXME: DerefMut
-            (TyKind::Ref(.., st1), TyKind::Ref(.., st2)) => {
-                self.unify_autoderef_behind_ref(st1, st2)
-            }
+            (TyKind::Ref(_, st1), TyKind::Ref(_, st2)) => self.unify_autoderef_behind_ref(st1, st2),
 
             // Otherwise, normal unify
             _ => self.unify(&from_ty, to_ty),
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 53d94fd0da..2fcc7c5491 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -15,11 +15,11 @@ use stdx::always;
 use syntax::ast::RangeOp;
 
 use crate::{
-    autoderef, dummy_usize_const,
+    autoderef,
     lower::lower_to_chalk_mutability,
     method_resolution, op,
     primitive::{self, UintTy},
-    static_lifetime, to_chalk_trait_id,
+    to_chalk_trait_id,
     traits::{chalk::from_chalk, FnTrait},
     utils::{generics, variant_data, Generics},
     AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, InEnvironment, Interner,
@@ -317,7 +317,13 @@ impl<'a> InferenceContext<'a> {
                 self.normalize_associated_types_in(ret_ty)
             }
             Expr::MethodCall { receiver, args, method_name, generic_args } => self
-                .infer_method_call(tgt_expr, *receiver, &args, &method_name, generic_args.as_ref()),
+                .infer_method_call(
+                    tgt_expr,
+                    *receiver,
+                    &args,
+                    &method_name,
+                    generic_args.as_deref(),
+                ),
             Expr::Match { expr, arms } => {
                 let input_ty = self.infer_expr(*expr, &Expectation::none());
 
@@ -398,7 +404,7 @@ impl<'a> InferenceContext<'a> {
                 TyKind::Never.intern(&Interner)
             }
             Expr::RecordLit { path, fields, spread } => {
-                let (ty, def_id) = self.resolve_variant(path.as_ref());
+                let (ty, def_id) = self.resolve_variant(path.as_deref());
                 if let Some(variant) = def_id {
                     self.write_variant_resolution(tgt_expr.into(), variant);
                 }
@@ -527,7 +533,7 @@ impl<'a> InferenceContext<'a> {
                 let inner_ty = self.infer_expr_inner(*expr, &expectation);
                 match rawness {
                     Rawness::RawPtr => TyKind::Raw(mutability, inner_ty),
-                    Rawness::Ref => TyKind::Ref(mutability, static_lifetime(), inner_ty),
+                    Rawness::Ref => TyKind::Ref(mutability, inner_ty),
                 }
                 .intern(&Interner)
             }
@@ -702,7 +708,7 @@ impl<'a> InferenceContext<'a> {
             }
             Expr::Array(array) => {
                 let elem_ty = match expected.ty.kind(&Interner) {
-                    TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(),
+                    TyKind::Array(st) | TyKind::Slice(st) => st.clone(),
                     _ => self.table.new_type_var(),
                 };
 
@@ -726,19 +732,17 @@ impl<'a> InferenceContext<'a> {
                     }
                 }
 
-                TyKind::Array(elem_ty, dummy_usize_const()).intern(&Interner)
+                TyKind::Array(elem_ty).intern(&Interner)
             }
             Expr::Literal(lit) => match lit {
                 Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner),
                 Literal::String(..) => {
-                    TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(&Interner))
-                        .intern(&Interner)
+                    TyKind::Ref(Mutability::Not, TyKind::Str.intern(&Interner)).intern(&Interner)
                 }
                 Literal::ByteString(..) => {
                     let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(&Interner);
-                    let array_type =
-                        TyKind::Array(byte_type, dummy_usize_const()).intern(&Interner);
-                    TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(&Interner)
+                    let array_type = TyKind::Array(byte_type).intern(&Interner);
+                    TyKind::Ref(Mutability::Not, array_type).intern(&Interner)
                 }
                 Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(&Interner),
                 Literal::Int(_v, ty) => match ty {
@@ -874,9 +878,7 @@ impl<'a> InferenceContext<'a> {
         // Apply autoref so the below unification works correctly
         // FIXME: return correct autorefs from lookup_method
         let actual_receiver_ty = match expected_receiver_ty.as_reference() {
-            Some((_, lifetime, mutability)) => {
-                TyKind::Ref(mutability, lifetime, derefed_receiver_ty).intern(&Interner)
-            }
+            Some((_, mutability)) => TyKind::Ref(mutability, derefed_receiver_ty).intern(&Interner),
             _ => derefed_receiver_ty,
         };
         self.unify(&expected_receiver_ty, &actual_receiver_ty);
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index c1d7a6b763..afaf6b28ba 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -13,8 +13,8 @@ use hir_expand::name::Name;
 
 use super::{BindingMode, Expectation, InferenceContext};
 use crate::{
-    lower::lower_to_chalk_mutability, static_lifetime, utils::variant_data, Interner, Substitution,
-    Ty, TyBuilder, TyKind,
+    lower::lower_to_chalk_mutability, utils::variant_data, Interner, Substitution, Ty, TyBuilder,
+    TyKind,
 };
 
 impl<'a> InferenceContext<'a> {
@@ -104,7 +104,7 @@ impl<'a> InferenceContext<'a> {
         let body = Arc::clone(&self.body); // avoid borrow checker problem
 
         if is_non_ref_pat(&body, pat) {
-            while let Some((inner, _lifetime, mutability)) = expected.as_reference() {
+            while let Some((inner, mutability)) = expected.as_reference() {
                 expected = inner;
                 default_bm = match default_bm {
                     BindingMode::Move => BindingMode::Ref(mutability),
@@ -162,7 +162,7 @@ impl<'a> InferenceContext<'a> {
             Pat::Ref { pat, mutability } => {
                 let mutability = lower_to_chalk_mutability(*mutability);
                 let expectation = match expected.as_reference() {
-                    Some((inner_ty, _lifetime, exp_mut)) => {
+                    Some((inner_ty, exp_mut)) => {
                         if mutability != exp_mut {
                             // FIXME: emit type error?
                         }
@@ -171,10 +171,10 @@ impl<'a> InferenceContext<'a> {
                     _ => self.result.standard_types.unknown.clone(),
                 };
                 let subty = self.infer_pat(*pat, &expectation, default_bm);
-                TyKind::Ref(mutability, static_lifetime(), subty).intern(&Interner)
+                TyKind::Ref(mutability, subty).intern(&Interner)
             }
             Pat::TupleStruct { path: p, args: subpats, ellipsis } => self.infer_tuple_struct_pat(
-                p.as_ref(),
+                p.as_deref(),
                 subpats,
                 expected,
                 default_bm,
@@ -182,7 +182,7 @@ impl<'a> InferenceContext<'a> {
                 *ellipsis,
             ),
             Pat::Record { path: p, args: fields, ellipsis: _ } => {
-                self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
+                self.infer_record_pat(p.as_deref(), fields, expected, default_bm, pat)
             }
             Pat::Path(path) => {
                 // FIXME use correct resolver for the surrounding expression
@@ -204,8 +204,7 @@ impl<'a> InferenceContext<'a> {
 
                 let bound_ty = match mode {
                     BindingMode::Ref(mutability) => {
-                        TyKind::Ref(mutability, static_lifetime(), inner_ty.clone())
-                            .intern(&Interner)
+                        TyKind::Ref(mutability, inner_ty.clone()).intern(&Interner)
                     }
                     BindingMode::Move => inner_ty.clone(),
                 };
@@ -214,20 +213,17 @@ impl<'a> InferenceContext<'a> {
                 return inner_ty;
             }
             Pat::Slice { prefix, slice, suffix } => {
-                let elem_ty = match expected.kind(&Interner) {
-                    TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(),
-                    _ => self.err_ty(),
+                let (container_ty, elem_ty): (fn(_) -> _, _) = match expected.kind(&Interner) {
+                    TyKind::Array(st) => (TyKind::Array, st.clone()),
+                    TyKind::Slice(st) => (TyKind::Slice, st.clone()),
+                    _ => (TyKind::Slice, self.err_ty()),
                 };
 
                 for pat_id in prefix.iter().chain(suffix) {
                     self.infer_pat(*pat_id, &elem_ty, default_bm);
                 }
 
-                let pat_ty = match expected.kind(&Interner) {
-                    TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()),
-                    _ => TyKind::Slice(elem_ty),
-                }
-                .intern(&Interner);
+                let pat_ty = container_ty(elem_ty).intern(&Interner);
                 if let Some(slice_pat_id) = slice {
                     self.infer_pat(*slice_pat_id, &pat_ty, default_bm);
                 }
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index 7d76cda684..2f9523325c 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -317,11 +317,9 @@ impl InferenceTable {
                 | (TyKind::Closure(.., substs1), TyKind::Closure(.., substs2)) => {
                     self.unify_substs(substs1, substs2, depth + 1)
                 }
-                (TyKind::Array(ty1, c1), TyKind::Array(ty2, c2)) if c1 == c2 => {
-                    self.unify_inner(ty1, ty2, depth + 1)
-                }
-                (TyKind::Ref(_, _, ty1), TyKind::Ref(_, _, ty2))
+                (TyKind::Ref(_, ty1), TyKind::Ref(_, ty2))
                 | (TyKind::Raw(_, ty1), TyKind::Raw(_, ty2))
+                | (TyKind::Array(ty1), TyKind::Array(ty2))
                 | (TyKind::Slice(ty1), TyKind::Slice(ty2)) => self.unify_inner(ty1, ty2, depth + 1),
                 _ => true, /* we checked equals_ctor already */
             }
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index a2a5bcc07a..c3ec123522 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -30,13 +30,12 @@ mod test_db;
 
 use std::sync::Arc;
 
-use chalk_ir::UintTy;
 use itertools::Itertools;
 
 use base_db::salsa;
 use hir_def::{
-    expr::ExprId, type_ref::Rawness, AssocContainerId, ConstParamId, FunctionId, GenericDefId,
-    HasModule, LifetimeParamId, Lookup, TraitId, TypeAliasId, TypeParamId,
+    expr::ExprId, type_ref::Rawness, AssocContainerId, FunctionId, GenericDefId, HasModule,
+    LifetimeParamId, Lookup, TraitId, TypeAliasId, TypeParamId,
 };
 
 use crate::{db::HirDatabase, display::HirDisplay, utils::generics};
@@ -72,11 +71,6 @@ pub type Lifetime = chalk_ir::Lifetime<Interner>;
 pub type LifetimeData = chalk_ir::LifetimeData<Interner>;
 pub type LifetimeOutlives = chalk_ir::LifetimeOutlives<Interner>;
 
-pub type Const = chalk_ir::Const<Interner>;
-pub type ConstData = chalk_ir::ConstData<Interner>;
-pub type ConstValue = chalk_ir::ConstValue<Interner>;
-pub type ConcreteConst = chalk_ir::ConcreteConst<Interner>;
-
 pub type ChalkTraitId = chalk_ir::TraitId<Interner>;
 
 pub type FnSig = chalk_ir::FnSig<Interner>;
@@ -171,16 +165,16 @@ impl CallableSig {
 }
 
 impl Ty {
-    pub fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)> {
+    pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
         match self.kind(&Interner) {
-            TyKind::Ref(mutability, lifetime, ty) => Some((ty, *lifetime, *mutability)),
+            TyKind::Ref(mutability, ty) => Some((ty, *mutability)),
             _ => None,
         }
     }
 
     pub fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)> {
         match self.kind(&Interner) {
-            TyKind::Ref(mutability, _, ty) => Some((ty, Rawness::Ref, *mutability)),
+            TyKind::Ref(mutability, ty) => Some((ty, Rawness::Ref, *mutability)),
             TyKind::Raw(mutability, ty) => Some((ty, Rawness::RawPtr, *mutability)),
             _ => None,
         }
@@ -189,7 +183,7 @@ impl Ty {
     pub fn strip_references(&self) -> &Ty {
         let mut t: &Ty = self;
 
-        while let TyKind::Ref(_mutability, _lifetime, ty) = t.kind(&Interner) {
+        while let TyKind::Ref(_mutability, ty) = t.kind(&Interner) {
             t = ty;
         }
 
@@ -233,9 +227,7 @@ impl Ty {
     pub fn equals_ctor(&self, other: &Ty) -> bool {
         match (self.kind(&Interner), other.kind(&Interner)) {
             (TyKind::Adt(adt, ..), TyKind::Adt(adt2, ..)) => adt == adt2,
-            (TyKind::Slice(_), TyKind::Slice(_)) | (TyKind::Array(_, _), TyKind::Array(_, _)) => {
-                true
-            }
+            (TyKind::Slice(_), TyKind::Slice(_)) | (TyKind::Array(_), TyKind::Array(_)) => true,
             (TyKind::FnDef(def_id, ..), TyKind::FnDef(def_id2, ..)) => def_id == def_id2,
             (TyKind::OpaqueType(ty_id, ..), TyKind::OpaqueType(ty_id2, ..)) => ty_id == ty_id2,
             (TyKind::AssociatedType(ty_id, ..), TyKind::AssociatedType(ty_id2, ..)) => {
@@ -496,12 +488,6 @@ pub fn lt_from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> L
     db.lookup_intern_lifetime_param_id(interned_id)
 }
 
-pub fn const_from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> ConstParamId {
-    assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT);
-    let interned_id = salsa::InternKey::from_intern_id(salsa::InternId::from(idx.idx));
-    db.lookup_intern_const_param_id(interned_id)
-}
-
 pub fn to_chalk_trait_id(id: TraitId) -> ChalkTraitId {
     chalk_ir::TraitId(salsa::InternKey::as_intern_id(&id))
 }
@@ -509,16 +495,3 @@ pub fn to_chalk_trait_id(id: TraitId) -> ChalkTraitId {
 pub fn from_chalk_trait_id(id: ChalkTraitId) -> TraitId {
     salsa::InternKey::from_intern_id(id.0)
 }
-
-pub fn static_lifetime() -> Lifetime {
-    LifetimeData::Static.intern(&Interner)
-}
-
-pub fn dummy_usize_const() -> Const {
-    let usize_ty = chalk_ir::TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner);
-    chalk_ir::ConstData {
-        ty: usize_ty,
-        value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: () }),
-    }
-    .intern(&Interner)
-}
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 8be1bcddb6..3cbb6ad54b 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -27,7 +27,7 @@ use stdx::impl_from;
 
 use crate::{
     db::HirDatabase,
-    dummy_usize_const, static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
+    to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
     traits::chalk::{Interner, ToChalk},
     utils::{
         all_super_trait_refs, associated_type_by_name_including_super_traits, generics,
@@ -166,7 +166,7 @@ impl<'a> TyLoweringContext<'a> {
             }
             TypeRef::Array(inner) => {
                 let inner_ty = self.lower_ty(inner);
-                TyKind::Array(inner_ty, dummy_usize_const()).intern(&Interner)
+                TyKind::Array(inner_ty).intern(&Interner)
             }
             TypeRef::Slice(inner) => {
                 let inner_ty = self.lower_ty(inner);
@@ -174,9 +174,7 @@ impl<'a> TyLoweringContext<'a> {
             }
             TypeRef::Reference(inner, _, mutability) => {
                 let inner_ty = self.lower_ty(inner);
-                let lifetime = static_lifetime();
-                TyKind::Ref(lower_to_chalk_mutability(*mutability), lifetime, inner_ty)
-                    .intern(&Interner)
+                TyKind::Ref(lower_to_chalk_mutability(*mutability), inner_ty).intern(&Interner)
             }
             TypeRef::Placeholder => TyKind::Error.intern(&Interner),
             TypeRef::Fn(params, is_varargs) => {
@@ -200,7 +198,7 @@ impl<'a> TyLoweringContext<'a> {
                     )
                 });
                 let bounds = crate::make_only_type_binders(1, bounds);
-                TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(&Interner)
+                TyKind::Dyn(DynTy { bounds }).intern(&Interner)
             }
             TypeRef::ImplTrait(bounds) => {
                 match self.impl_trait_mode {
@@ -392,7 +390,6 @@ impl<'a> TyLoweringContext<'a> {
                                 ))),
                             ),
                         ),
-                        lifetime: static_lifetime(),
                     };
                     TyKind::Dyn(dyn_ty).intern(&Interner)
                 };
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index 5042bfbcad..6d65d3eb9e 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -19,7 +19,6 @@ use crate::{
     db::HirDatabase,
     from_foreign_def_id,
     primitive::{self, FloatTy, IntTy, UintTy},
-    static_lifetime,
     utils::all_super_traits,
     AdtId, Canonical, CanonicalVarKinds, DebruijnIndex, FnPointer, FnSig, ForeignDefId,
     InEnvironment, Interner, Scalar, Substitution, TraitEnvironment, Ty, TyBuilder, TyKind,
@@ -454,8 +453,7 @@ fn iterate_method_candidates_with_autoref(
     }
     let refed = Canonical {
         binders: deref_chain[0].binders.clone(),
-        value: TyKind::Ref(Mutability::Not, static_lifetime(), deref_chain[0].value.clone())
-            .intern(&Interner),
+        value: TyKind::Ref(Mutability::Not, deref_chain[0].value.clone()).intern(&Interner),
     };
     if iterate_method_candidates_by_receiver(
         &refed,
@@ -472,8 +470,7 @@ fn iterate_method_candidates_with_autoref(
     }
     let ref_muted = Canonical {
         binders: deref_chain[0].binders.clone(),
-        value: TyKind::Ref(Mutability::Mut, static_lifetime(), deref_chain[0].value.clone())
-            .intern(&Interner),
+        value: TyKind::Ref(Mutability::Mut, deref_chain[0].value.clone()).intern(&Interner),
     };
     if iterate_method_candidates_by_receiver(
         &ref_muted,
@@ -842,9 +839,7 @@ fn autoderef_method_receiver(
 ) -> Vec<Canonical<Ty>> {
     let mut deref_chain: Vec<_> = autoderef::autoderef(db, Some(krate), ty).collect();
     // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
-    if let Some(TyKind::Array(parameters, _)) =
-        deref_chain.last().map(|ty| ty.value.kind(&Interner))
-    {
+    if let Some(TyKind::Array(parameters)) = deref_chain.last().map(|ty| ty.value.kind(&Interner)) {
         let kinds = deref_chain.last().unwrap().binders.clone();
         let unsized_ty = TyKind::Slice(parameters.clone()).intern(&Interner);
         deref_chain.push(Canonical { value: unsized_ty, binders: kinds })
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs
index cf73cb0784..3047fbacb3 100644
--- a/crates/hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/hir_ty/src/traits/chalk/mapping.rs
@@ -3,17 +3,16 @@
 //! Chalk (in both directions); plus some helper functions for more specialized
 //! conversions.
 
-use chalk_ir::{cast::Cast, interner::HasInterner};
+use chalk_ir::{cast::Cast, interner::HasInterner, LifetimeData};
 use chalk_solve::rust_ir;
 
 use base_db::salsa::InternKey;
 use hir_def::{GenericDefId, TypeAliasId};
 
 use crate::{
-    chalk_ext::ProjectionTyExt, db::HirDatabase, dummy_usize_const, static_lifetime, AliasTy,
-    CallableDefId, Canonical, Const, DomainGoal, FnPointer, GenericArg, InEnvironment, Lifetime,
-    OpaqueTy, ProjectionTy, QuantifiedWhereClause, Substitution, TraitRef, Ty, TypeWalk,
-    WhereClause,
+    chalk_ext::ProjectionTyExt, db::HirDatabase, primitive::UintTy, AliasTy, CallableDefId,
+    Canonical, DomainGoal, FnPointer, GenericArg, InEnvironment, OpaqueTy, ProjectionTy,
+    QuantifiedWhereClause, Scalar, Substitution, TraitRef, Ty, TypeWalk, WhereClause,
 };
 
 use super::interner::*;
@@ -23,8 +22,8 @@ impl ToChalk for Ty {
     type Chalk = chalk_ir::Ty<Interner>;
     fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty<Interner> {
         match self.into_inner() {
-            TyKind::Ref(m, lt, ty) => ref_to_chalk(db, m, lt, ty),
-            TyKind::Array(ty, size) => array_to_chalk(db, ty, size),
+            TyKind::Ref(m, ty) => ref_to_chalk(db, m, ty),
+            TyKind::Array(ty) => array_to_chalk(db, ty),
             TyKind::Function(FnPointer { sig, substitution: substs, .. }) => {
                 let substitution = chalk_ir::FnSubst(substs.0.to_chalk(db));
                 chalk_ir::TyKind::Function(chalk_ir::FnPointer {
@@ -101,7 +100,7 @@ impl ToChalk for Ty {
                 );
                 let bounded_ty = chalk_ir::DynTy {
                     bounds: chalk_ir::Binders::new(binders, where_clauses),
-                    lifetime: static_lifetime(),
+                    lifetime: LifetimeData::Static.intern(&Interner),
                 };
                 chalk_ir::TyKind::Dyn(bounded_ty).intern(&Interner)
             }
@@ -111,7 +110,7 @@ impl ToChalk for Ty {
     fn from_chalk(db: &dyn HirDatabase, chalk: chalk_ir::Ty<Interner>) -> Self {
         match chalk.data(&Interner).kind.clone() {
             chalk_ir::TyKind::Error => TyKind::Error,
-            chalk_ir::TyKind::Array(ty, size) => TyKind::Array(from_chalk(db, ty), size),
+            chalk_ir::TyKind::Array(ty, _size) => TyKind::Array(from_chalk(db, ty)),
             chalk_ir::TyKind::Placeholder(idx) => TyKind::Placeholder(idx),
             chalk_ir::TyKind::Alias(chalk_ir::AliasTy::Projection(proj)) => {
                 let associated_ty = proj.associated_ty_id;
@@ -150,7 +149,6 @@ impl ToChalk for Ty {
                         where_clauses.bounds.binders.clone(),
                         crate::QuantifiedWhereClauses::from_iter(&Interner, bounds),
                     ),
-                    lifetime: static_lifetime(),
                 })
             }
 
@@ -169,8 +167,8 @@ impl ToChalk for Ty {
             }
             chalk_ir::TyKind::Raw(mutability, ty) => TyKind::Raw(mutability, from_chalk(db, ty)),
             chalk_ir::TyKind::Slice(ty) => TyKind::Slice(from_chalk(db, ty)),
-            chalk_ir::TyKind::Ref(mutability, lifetime, ty) => {
-                TyKind::Ref(mutability, lifetime, from_chalk(db, ty))
+            chalk_ir::TyKind::Ref(mutability, _lifetime, ty) => {
+                TyKind::Ref(mutability, from_chalk(db, ty))
             }
             chalk_ir::TyKind::Str => TyKind::Str,
             chalk_ir::TyKind::Never => TyKind::Never,
@@ -194,19 +192,24 @@ impl ToChalk for Ty {
 fn ref_to_chalk(
     db: &dyn HirDatabase,
     mutability: chalk_ir::Mutability,
-    _lifetime: Lifetime,
     ty: Ty,
 ) -> chalk_ir::Ty<Interner> {
     let arg = ty.to_chalk(db);
-    let lifetime = static_lifetime();
+    let lifetime = LifetimeData::Static.intern(&Interner);
     chalk_ir::TyKind::Ref(mutability, lifetime, arg).intern(&Interner)
 }
 
 /// We currently don't model constants, but Chalk does. So, we have to insert a
 /// fake constant here, because Chalks built-in logic may expect it to be there.
-fn array_to_chalk(db: &dyn HirDatabase, ty: Ty, _: Const) -> chalk_ir::Ty<Interner> {
+fn array_to_chalk(db: &dyn HirDatabase, ty: Ty) -> chalk_ir::Ty<Interner> {
     let arg = ty.to_chalk(db);
-    chalk_ir::TyKind::Array(arg, dummy_usize_const()).intern(&Interner)
+    let usize_ty = chalk_ir::TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner);
+    let const_ = chalk_ir::ConstData {
+        ty: usize_ty,
+        value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: () }),
+    }
+    .intern(&Interner);
+    chalk_ir::TyKind::Array(arg, const_).intern(&Interner)
 }
 
 impl ToChalk for GenericArg {
diff --git a/crates/hir_ty/src/types.rs b/crates/hir_ty/src/types.rs
index 89c0ddd1a4..d4e07a6b8c 100644
--- a/crates/hir_ty/src/types.rs
+++ b/crates/hir_ty/src/types.rs
@@ -10,9 +10,8 @@ use chalk_ir::{
 use smallvec::SmallVec;
 
 use crate::{
-    AssocTypeId, CanonicalVarKinds, ChalkTraitId, ClosureId, Const, FnDefId, FnSig, ForeignDefId,
-    InferenceVar, Interner, Lifetime, OpaqueTyId, PlaceholderIndex, TypeWalk, VariableKind,
-    VariableKinds,
+    AssocTypeId, CanonicalVarKinds, ChalkTraitId, ClosureId, FnDefId, FnSig, ForeignDefId,
+    InferenceVar, Interner, OpaqueTyId, PlaceholderIndex, TypeWalk, VariableKind, VariableKinds,
 };
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
@@ -40,7 +39,6 @@ impl ProjectionTy {
 pub struct DynTy {
     /// The unknown self type.
     pub bounds: Binders<QuantifiedWhereClauses>,
-    pub lifetime: Lifetime,
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
@@ -114,7 +112,7 @@ pub enum TyKind {
     Tuple(usize, Substitution),
 
     /// An array with the given length. Written as `[T; n]`.
-    Array(Ty, Const),
+    Array(Ty),
 
     /// The pointee of an array slice.  Written as `[T]`.
     Slice(Ty),
@@ -124,7 +122,7 @@ pub enum TyKind {
 
     /// A reference; a pointer with an associated lifetime. Written as
     /// `&'a mut T` or `&'a T`.
-    Ref(Mutability, Lifetime, Ty),
+    Ref(Mutability, Ty),
 
     /// This represents a placeholder for an opaque type in situations where we
     /// don't know the hidden type (i.e. currently almost always). This is
diff --git a/crates/hir_ty/src/walk.rs b/crates/hir_ty/src/walk.rs
index 41ebf6137c..e1e77ba37a 100644
--- a/crates/hir_ty/src/walk.rs
+++ b/crates/hir_ty/src/walk.rs
@@ -153,10 +153,7 @@ impl TypeWalk for Ty {
                     p.walk(f);
                 }
             }
-            TyKind::Slice(ty)
-            | TyKind::Array(ty, _)
-            | TyKind::Ref(_, _, ty)
-            | TyKind::Raw(_, ty) => {
+            TyKind::Slice(ty) | TyKind::Array(ty) | TyKind::Ref(_, ty) | TyKind::Raw(_, ty) => {
                 ty.walk(f);
             }
             TyKind::Function(fn_pointer) => {
@@ -190,10 +187,7 @@ impl TypeWalk for Ty {
             TyKind::Alias(AliasTy::Opaque(o_ty)) => {
                 o_ty.substitution.walk_mut_binders(f, binders);
             }
-            TyKind::Slice(ty)
-            | TyKind::Array(ty, _)
-            | TyKind::Ref(_, _, ty)
-            | TyKind::Raw(_, ty) => {
+            TyKind::Slice(ty) | TyKind::Array(ty) | TyKind::Ref(_, ty) | TyKind::Raw(_, ty) => {
                 ty.walk_mut_binders(f, binders);
             }
             TyKind::Function(fn_pointer) => {
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index c2c87b207b..f7050be4e7 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -52,7 +52,7 @@ pub struct BuildDataCollector {
     configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
 }
 
-#[derive(Debug, Default, PartialEq, Eq, Clone)]
+#[derive(Debug, Default, PartialEq, Eq)]
 pub struct BuildDataResult {
     data: FxHashMap<AbsPathBuf, BuildDataMap>,
 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e012b4452f..cda272fd48 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -445,8 +445,8 @@ impl Config {
     pub fn hover_actions(&self) -> bool {
         self.experimental("hoverActions")
     }
-    pub fn server_status_notification(&self) -> bool {
-        self.experimental("serverStatusNotification")
+    pub fn status_notification(&self) -> bool {
+        self.experimental("statusNotification")
     }
 
     pub fn publish_diagnostics(&self) -> bool {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index adeb7a97e8..8679c85996 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -23,7 +23,6 @@ use crate::{
     document::DocumentData,
     from_proto,
     line_index::{LineEndings, LineIndex},
-    lsp_ext,
     main_loop::Task,
     op_queue::OpQueue,
     reload::SourceRootConfig,
@@ -33,6 +32,20 @@ use crate::{
     Result,
 };
 
+#[derive(Eq, PartialEq, Copy, Clone)]
+pub(crate) enum Status {
+    Loading,
+    Ready { partial: bool },
+    Invalid,
+    NeedsReload,
+}
+
+impl Default for Status {
+    fn default() -> Self {
+        Status::Loading
+    }
+}
+
 // Enforces drop order
 pub(crate) struct Handle<H, C> {
     pub(crate) handle: H,
@@ -54,36 +67,26 @@ pub(crate) struct GlobalState {
     req_queue: ReqQueue,
     pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
     pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
+    pub(crate) vfs_config_version: u32,
+    pub(crate) flycheck: Vec<FlycheckHandle>,
+    pub(crate) flycheck_sender: Sender<flycheck::Message>,
+    pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
     pub(crate) config: Arc<Config>,
     pub(crate) analysis_host: AnalysisHost,
     pub(crate) diagnostics: DiagnosticCollection,
     pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
     pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
+    pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
     pub(crate) shutdown_requested: bool,
-    pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
+    pub(crate) status: Status,
     pub(crate) source_root_config: SourceRootConfig,
     pub(crate) proc_macro_client: Option<ProcMacroClient>,
 
-    pub(crate) flycheck: Vec<FlycheckHandle>,
-    pub(crate) flycheck_sender: Sender<flycheck::Message>,
-    pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
-
-    pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
-    pub(crate) vfs_config_version: u32,
-    pub(crate) vfs_progress_config_version: u32,
-    pub(crate) vfs_progress_n_total: usize,
-    pub(crate) vfs_progress_n_done: usize,
-
-    /// For both `workspaces` and `workspace_build_data`, the field stores the
-    /// data we actually use, while the `OpQueue` stores the result of the last
-    /// fetch.
-    ///
-    /// If the fetch (partially) fails, we do not update the values.
     pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
-    pub(crate) fetch_workspaces_queue: OpQueue<(), Vec<anyhow::Result<ProjectWorkspace>>>,
+    pub(crate) fetch_workspaces_queue: OpQueue<(), ()>,
+
     pub(crate) workspace_build_data: Option<BuildDataResult>,
-    pub(crate) fetch_build_data_queue:
-        OpQueue<BuildDataCollector, Option<anyhow::Result<BuildDataResult>>>,
+    pub(crate) fetch_build_data_queue: OpQueue<BuildDataCollector, ()>,
 
     latest_requests: Arc<RwLock<LatestRequests>>,
 }
@@ -121,32 +124,25 @@ impl GlobalState {
         GlobalState {
             sender,
             req_queue: ReqQueue::default(),
+            vfs_config_version: 0,
             task_pool,
             loader,
+            flycheck: Vec::new(),
+            flycheck_sender,
+            flycheck_receiver,
             config: Arc::new(config),
             analysis_host,
             diagnostics: Default::default(),
             mem_docs: FxHashMap::default(),
             semantic_tokens_cache: Arc::new(Default::default()),
+            vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
             shutdown_requested: false,
-            last_reported_status: None,
+            status: Status::default(),
             source_root_config: SourceRootConfig::default(),
             proc_macro_client: None,
-
-            flycheck: Vec::new(),
-            flycheck_sender,
-            flycheck_receiver,
-
-            vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
-            vfs_config_version: 0,
-            vfs_progress_config_version: 0,
-            vfs_progress_n_total: 0,
-            vfs_progress_n_done: 0,
-
             workspaces: Arc::new(Vec::new()),
             fetch_workspaces_queue: OpQueue::default(),
             workspace_build_data: None,
-
             fetch_build_data_queue: OpQueue::default(),
             latest_requests: Default::default(),
         }
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 81a6f22f16..0e1fec2095 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -241,26 +241,26 @@ pub struct SsrParams {
     pub selections: Vec<lsp_types::Range>,
 }
 
-pub enum ServerStatusNotification {}
+pub enum StatusNotification {}
 
-impl Notification for ServerStatusNotification {
-    type Params = ServerStatusParams;
-    const METHOD: &'static str = "experimental/serverStatus";
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum Status {
+    Loading,
+    ReadyPartial,
+    Ready,
+    NeedsReload,
+    Invalid,
 }
 
-#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
-pub struct ServerStatusParams {
-    pub health: Health,
-    pub quiescent: bool,
-    pub message: Option<String>,
+#[derive(Deserialize, Serialize)]
+pub struct StatusParams {
+    pub status: Status,
 }
 
-#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub enum Health {
-    Ok,
-    Warning,
-    Error,
+impl Notification for StatusNotification {
+    type Params = StatusParams;
+    const METHOD: &'static str = "rust-analyzer/status";
 }
 
 pub enum CodeActionRequest {}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index a5655116b4..e88f16cc11 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -2,7 +2,6 @@
 //! requests/replies and notifications back to the client.
 use std::{
     env, fmt,
-    sync::Arc,
     time::{Duration, Instant},
 };
 
@@ -13,7 +12,6 @@ use ide::{Canceled, FileId};
 use ide_db::base_db::VfsPath;
 use lsp_server::{Connection, Notification, Request, Response};
 use lsp_types::notification::Notification as _;
-use project_model::BuildDataCollector;
 use vfs::ChangeKind;
 
 use crate::{
@@ -21,7 +19,7 @@ use crate::{
     dispatch::{NotificationDispatcher, RequestDispatcher},
     document::DocumentData,
     from_proto,
-    global_state::{file_id_to_url, url_to_file_id, GlobalState},
+    global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
     handlers, lsp_ext,
     lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
     reload::{BuildDataProgress, ProjectWorkspaceProgress},
@@ -189,7 +187,7 @@ impl GlobalState {
             log::info!("task queue len: {}", task_queue_len);
         }
 
-        let was_quiescent = self.is_quiescent();
+        let mut new_status = self.status;
         match event {
             Event::Lsp(msg) => match msg {
                 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
@@ -229,24 +227,11 @@ impl GlobalState {
                                     (Progress::Report, Some(msg))
                                 }
                                 ProjectWorkspaceProgress::End(workspaces) => {
-                                    self.fetch_workspaces_completed(workspaces);
-
-                                    let old = Arc::clone(&self.workspaces);
-                                    self.switch_workspaces();
-                                    let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
-
-                                    if self.config.run_build_scripts() && workspaces_updated {
-                                        let mut collector = BuildDataCollector::default();
-                                        for ws in self.workspaces.iter() {
-                                            ws.collect_build_data_configs(&mut collector);
-                                        }
-                                        self.fetch_build_data_request(collector)
-                                    }
-
+                                    self.fetch_workspaces_completed();
+                                    self.switch_workspaces(workspaces, None);
                                     (Progress::End, None)
                                 }
                             };
-
                             self.report_progress("fetching", state, msg, None);
                         }
                         Task::FetchBuildData(progress) => {
@@ -255,21 +240,19 @@ impl GlobalState {
                                 BuildDataProgress::Report(msg) => {
                                     (Some(Progress::Report), Some(msg))
                                 }
-                                BuildDataProgress::End(build_data_result) => {
-                                    self.fetch_build_data_completed(build_data_result);
-
-                                    self.switch_workspaces();
-
+                                BuildDataProgress::End(collector) => {
+                                    self.fetch_build_data_completed();
+                                    let workspaces =
+                                        (*self.workspaces).clone().into_iter().map(Ok).collect();
+                                    self.switch_workspaces(workspaces, Some(collector));
                                     (Some(Progress::End), None)
                                 }
                             };
-
                             if let Some(state) = state {
                                 self.report_progress("loading", state, msg, None);
                             }
                         }
                     }
-
                     // Coalesce multiple task events into one loop turn
                     task = match self.task_pool.receiver.try_recv() {
                         Ok(task) => task,
@@ -315,25 +298,30 @@ impl GlobalState {
                         }
                         vfs::loader::Message::Progress { n_total, n_done, config_version } => {
                             always!(config_version <= self.vfs_config_version);
-
-                            self.vfs_progress_config_version = config_version;
-                            self.vfs_progress_n_total = n_total;
-                            self.vfs_progress_n_done = n_done;
-
-                            let state = if n_done == 0 {
-                                Progress::Begin
-                            } else if n_done < n_total {
-                                Progress::Report
+                            if n_total == 0 {
+                                new_status = Status::Invalid;
                             } else {
-                                assert_eq!(n_done, n_total);
-                                Progress::End
-                            };
-                            self.report_progress(
-                                "roots scanned",
-                                state,
-                                Some(format!("{}/{}", n_done, n_total)),
-                                Some(Progress::fraction(n_done, n_total)),
-                            )
+                                let state = if n_done == 0 {
+                                    new_status = Status::Loading;
+                                    Progress::Begin
+                                } else if n_done < n_total {
+                                    Progress::Report
+                                } else {
+                                    assert_eq!(n_done, n_total);
+                                    new_status = Status::Ready {
+                                        partial: self.config.run_build_scripts()
+                                            && self.workspace_build_data.is_none()
+                                            || config_version < self.vfs_config_version,
+                                    };
+                                    Progress::End
+                                };
+                                self.report_progress(
+                                    "roots scanned",
+                                    state,
+                                    Some(format!("{}/{}", n_done, n_total)),
+                                    Some(Progress::fraction(n_done, n_total)),
+                                )
+                            }
                         }
                     }
                     // Coalesce many VFS event into a single loop turn
@@ -409,14 +397,18 @@ impl GlobalState {
         }
 
         let state_changed = self.process_changes();
-
-        if self.is_quiescent() && !was_quiescent {
+        let prev_status = self.status;
+        if prev_status != new_status {
+            self.transition(new_status);
+        }
+        let is_ready = matches!(self.status, Status::Ready { .. });
+        if prev_status == Status::Loading && is_ready {
             for flycheck in &self.flycheck {
                 flycheck.update();
             }
         }
 
-        if self.is_quiescent() && (!was_quiescent || state_changed) {
+        if is_ready && (state_changed || prev_status == Status::Loading) {
             self.update_file_notifications_on_threadpool();
 
             // Refresh semantic tokens if the client supports it.
@@ -445,13 +437,9 @@ impl GlobalState {
             }
         }
 
-        if self.config.cargo_autoreload() {
-            self.fetch_workspaces_if_needed();
-        }
+        self.fetch_workspaces_if_needed();
         self.fetch_build_data_if_needed();
 
-        self.report_new_status_if_needed();
-
         let loop_duration = loop_start.elapsed();
         if loop_duration > Duration::from_millis(100) {
             log::warn!("overly long loop turn: {:?}", loop_duration);
@@ -478,8 +466,7 @@ impl GlobalState {
             return Ok(());
         }
 
-        // Avoid flashing a bunch of unresolved references during initial load.
-        if self.workspaces.is_empty() && !self.is_quiescent() {
+        if self.status == Status::Loading && req.method != "shutdown" {
             self.respond(lsp_server::Response::new_err(
                 req.id,
                 // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion)
@@ -490,11 +477,7 @@ impl GlobalState {
         }
 
         RequestDispatcher { req: Some(req), global_state: self }
-            .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| {
-                s.fetch_workspaces_request();
-                s.fetch_workspaces_if_needed();
-                Ok(())
-            })?
+            .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces_request()))?
             .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
             .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
             .on_sync::<lsp_types::request::Shutdown>(|s, ()| {
diff --git a/crates/rust-analyzer/src/op_queue.rs b/crates/rust-analyzer/src/op_queue.rs
index 1d612a9337..f71b718bc0 100644
--- a/crates/rust-analyzer/src/op_queue.rs
+++ b/crates/rust-analyzer/src/op_queue.rs
@@ -2,27 +2,27 @@
 //! at a time.
 
 pub(crate) struct OpQueue<Args, Output> {
-    op_requested: Option<Args>,
+    op_scheduled: Option<Args>,
     op_in_progress: bool,
     last_op_result: Output,
 }
 
 impl<Args, Output: Default> Default for OpQueue<Args, Output> {
     fn default() -> Self {
-        Self { op_requested: None, op_in_progress: false, last_op_result: Default::default() }
+        Self { op_scheduled: None, op_in_progress: false, last_op_result: Default::default() }
     }
 }
 
 impl<Args, Output> OpQueue<Args, Output> {
     pub(crate) fn request_op(&mut self, data: Args) {
-        self.op_requested = Some(data);
+        self.op_scheduled = Some(data);
     }
     pub(crate) fn should_start_op(&mut self) -> Option<Args> {
         if self.op_in_progress {
             return None;
         }
-        self.op_in_progress = self.op_requested.is_some();
-        self.op_requested.take()
+        self.op_in_progress = self.op_scheduled.is_some();
+        self.op_scheduled.take()
     }
     pub(crate) fn op_completed(&mut self, result: Output) {
         assert!(self.op_in_progress);
@@ -34,10 +34,4 @@ impl<Args, Output> OpQueue<Args, Output> {
     pub(crate) fn last_op_result(&self) -> &Output {
         &self.last_op_result
     }
-    pub(crate) fn op_in_progress(&self) -> bool {
-        self.op_in_progress
-    }
-    pub(crate) fn op_requested(&self) -> bool {
-        self.op_requested.is_some()
-    }
 }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 301c7003b9..d12f891bbc 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -9,10 +9,11 @@ use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
 
 use crate::{
     config::{Config, FilesWatcher, LinkedProject},
-    global_state::GlobalState,
+    global_state::{GlobalState, Status},
     lsp_ext,
     main_loop::Task,
 };
+use lsp_ext::StatusParams;
 
 #[derive(Debug)]
 pub(crate) enum ProjectWorkspaceProgress {
@@ -29,13 +30,6 @@ pub(crate) enum BuildDataProgress {
 }
 
 impl GlobalState {
-    pub(crate) fn is_quiescent(&self) -> bool {
-        !(self.fetch_workspaces_queue.op_in_progress()
-            || self.fetch_build_data_queue.op_in_progress()
-            || self.vfs_progress_config_version < self.vfs_config_version
-            || self.vfs_progress_n_done < self.vfs_progress_n_total)
-    }
-
     pub(crate) fn update_configuration(&mut self, config: Config) {
         let _p = profile::span("GlobalState::update_configuration");
         let old_config = mem::replace(&mut self.config, Arc::new(config));
@@ -52,17 +46,25 @@ impl GlobalState {
         if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
             return;
         }
+        match self.status {
+            Status::Loading | Status::NeedsReload => return,
+            Status::Ready { .. } | Status::Invalid => (),
+        }
         log::info!(
-            "Requesting workspace reload because of the following changes: {}",
+            "Reloading workspace because of the following changes: {}",
             itertools::join(
                 changes
                     .iter()
                     .filter(|(path, kind)| is_interesting(path, *kind))
-                    .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
+                    .map(|(path, kind)| format!("{}/{:?}", path.display(), kind)),
                 ", "
             )
         );
-        self.fetch_workspaces_request();
+        if self.config.cargo_autoreload() {
+            self.fetch_workspaces_request();
+        } else {
+            self.transition(Status::NeedsReload);
+        }
 
         fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
             const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
@@ -99,32 +101,46 @@ impl GlobalState {
             false
         }
     }
-    pub(crate) fn report_new_status_if_needed(&mut self) {
-        if !self.config.server_status_notification() {
-            return;
+    pub(crate) fn transition(&mut self, new_status: Status) {
+        self.status = new_status;
+        if self.config.status_notification() {
+            let lsp_status = match new_status {
+                Status::Loading => lsp_ext::Status::Loading,
+                Status::Ready { partial: true } => lsp_ext::Status::ReadyPartial,
+                Status::Ready { partial: false } => lsp_ext::Status::Ready,
+                Status::Invalid => lsp_ext::Status::Invalid,
+                Status::NeedsReload => lsp_ext::Status::NeedsReload,
+            };
+            self.send_notification::<lsp_ext::StatusNotification>(StatusParams {
+                status: lsp_status,
+            });
         }
+    }
+
+    pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) {
+        self.fetch_build_data_queue.request_op(build_data_collector);
+    }
 
-        let mut status = lsp_ext::ServerStatusParams {
-            health: lsp_ext::Health::Ok,
-            quiescent: self.is_quiescent(),
-            message: None,
+    pub(crate) fn fetch_build_data_if_needed(&mut self) {
+        let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() {
+            Some(it) => it,
+            None => return,
         };
-        if !self.config.cargo_autoreload()
-            && self.is_quiescent()
-            && self.fetch_workspaces_queue.op_requested()
-        {
-            status.health = lsp_ext::Health::Warning;
-            status.message = Some("Workspace reload required".to_string())
-        }
-        if let Some(error) = self.loading_error() {
-            status.health = lsp_ext::Health::Error;
-            status.message = Some(format!("Workspace reload failed: {}", error))
-        }
+        self.task_pool.handle.spawn_with_sender(move |sender| {
+            sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
 
-        if self.last_reported_status.as_ref() != Some(&status) {
-            self.last_reported_status = Some(status.clone());
-            self.send_notification::<lsp_ext::ServerStatusNotification>(status);
-        }
+            let progress = {
+                let sender = sender.clone();
+                move |msg| {
+                    sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
+                }
+            };
+            let res = build_data_collector.collect(&progress);
+            sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
+        });
+    }
+    pub(crate) fn fetch_build_data_completed(&mut self) {
+        self.fetch_build_data_queue.op_completed(())
     }
 
     pub(crate) fn fetch_workspaces_request(&mut self) {
@@ -178,69 +194,57 @@ impl GlobalState {
             }
         });
     }
-    pub(crate) fn fetch_workspaces_completed(
-        &mut self,
-        workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
-    ) {
-        self.fetch_workspaces_queue.op_completed(workspaces)
+    pub(crate) fn fetch_workspaces_completed(&mut self) {
+        self.fetch_workspaces_queue.op_completed(())
     }
 
-    pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) {
-        self.fetch_build_data_queue.request_op(build_data_collector);
-    }
-    pub(crate) fn fetch_build_data_if_needed(&mut self) {
-        let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() {
-            Some(it) => it,
-            None => return,
-        };
-        self.task_pool.handle.spawn_with_sender(move |sender| {
-            sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
-
-            let progress = {
-                let sender = sender.clone();
-                move |msg| {
-                    sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
-                }
-            };
-            let res = build_data_collector.collect(&progress);
-            sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
-        });
-    }
-    pub(crate) fn fetch_build_data_completed(
+    pub(crate) fn switch_workspaces(
         &mut self,
-        build_data: anyhow::Result<BuildDataResult>,
+        workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
+        workspace_build_data: Option<anyhow::Result<BuildDataResult>>,
     ) {
-        self.fetch_build_data_queue.op_completed(Some(build_data))
-    }
-
-    pub(crate) fn switch_workspaces(&mut self) {
         let _p = profile::span("GlobalState::switch_workspaces");
-        log::info!("will switch workspaces");
-
-        if let Some(error_message) = self.loading_error() {
-            log::error!("failed to switch workspaces: {}", error_message);
-            self.show_message(lsp_types::MessageType::Error, error_message);
-            if !self.workspaces.is_empty() {
-                return;
-            }
-        }
+        log::info!("will switch workspaces: {:?}", workspaces);
 
-        let workspaces = self
-            .fetch_workspaces_queue
-            .last_op_result()
-            .iter()
-            .filter_map(|res| res.as_ref().ok().cloned())
+        let mut has_errors = false;
+        let workspaces = workspaces
+            .into_iter()
+            .filter_map(|res| {
+                res.map_err(|err| {
+                    has_errors = true;
+                    log::error!("failed to load workspace: {:#}", err);
+                    if self.workspaces.is_empty() {
+                        self.show_message(
+                            lsp_types::MessageType::Error,
+                            format!("rust-analyzer failed to load workspace: {:#}", err),
+                        );
+                    }
+                })
+                .ok()
+            })
             .collect::<Vec<_>>();
 
-        let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
-            Some(Ok(it)) => Some(it.clone()),
-            None | Some(Err(_)) => None,
+        let workspace_build_data = match workspace_build_data {
+            Some(Ok(it)) => Some(it),
+            Some(Err(err)) => {
+                log::error!("failed to fetch build data: {:#}", err);
+                self.show_message(
+                    lsp_types::MessageType::Error,
+                    format!("rust-analyzer failed to fetch build data: {:#}", err),
+                );
+                return;
+            }
+            None => None,
         };
 
         if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data {
             return;
         }
 
+        if !self.workspaces.is_empty() && has_errors {
+            return;
+        }
+
         if let FilesWatcher::Client = self.config.files().watcher {
             if self.config.did_change_watched_files_dynamic_registration() {
                 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
@@ -333,6 +337,14 @@ impl GlobalState {
         };
         change.set_crate_graph(crate_graph);
 
+        if self.config.run_build_scripts() && workspace_build_data.is_none() {
+            let mut collector = BuildDataCollector::default();
+            for ws in &workspaces {
+                ws.collect_build_data_configs(&mut collector);
+            }
+            self.fetch_build_data_request(collector)
+        }
+
         self.source_root_config = project_folders.source_root_config;
         self.workspaces = Arc::new(workspaces);
         self.workspace_build_data = workspace_build_data;
@@ -343,24 +355,6 @@ impl GlobalState {
         log::info!("did switch workspaces");
     }
 
-    fn loading_error(&self) -> Option<String> {
-        let mut message = None;
-
-        for ws in self.fetch_workspaces_queue.last_op_result() {
-            if let Err(err) = ws {
-                let message = message.get_or_insert_with(String::new);
-                stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
-            }
-        }
-
-        if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
-            let message = message.get_or_insert_with(String::new);
-            stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
-        }
-
-        message
-    }
-
     fn reload_flycheck(&mut self) {
         let _p = profile::span("GlobalState::reload_flycheck");
         let config = match self.config.flycheck() {
diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml
index c318a68f72..894944b18d 100644
--- a/crates/vfs/Cargo.toml
+++ b/crates/vfs/Cargo.toml
@@ -14,3 +14,4 @@ rustc-hash = "1.0"
 fst = "0.4"
 
 paths = { path = "../paths", version = "0.0.0" }
+indexmap = "1.6.2"
diff --git a/crates/vfs/src/path_interner.rs b/crates/vfs/src/path_interner.rs
index 2189e5e258..6e049f0d40 100644
--- a/crates/vfs/src/path_interner.rs
+++ b/crates/vfs/src/path_interner.rs
@@ -1,15 +1,22 @@
 //! Maps paths to compact integer ids. We don't care about clearings paths which
 //! no longer exist -- the assumption is total size of paths we ever look at is
 //! not too big.
-use rustc_hash::FxHashMap;
+use std::hash::BuildHasherDefault;
+
+use indexmap::IndexSet;
+use rustc_hash::FxHasher;
 
 use crate::{FileId, VfsPath};
 
 /// Structure to map between [`VfsPath`] and [`FileId`].
-#[derive(Default)]
 pub(crate) struct PathInterner {
-    map: FxHashMap<VfsPath, FileId>,
-    vec: Vec<VfsPath>,
+    map: IndexSet<VfsPath, BuildHasherDefault<FxHasher>>,
+}
+
+impl Default for PathInterner {
+    fn default() -> Self {
+        Self { map: IndexSet::default() }
+    }
 }
 
 impl PathInterner {
@@ -17,7 +24,7 @@ impl PathInterner {
     ///
     /// If `path` does not exists in `self`, returns [`None`].
     pub(crate) fn get(&self, path: &VfsPath) -> Option<FileId> {
-        self.map.get(path).copied()
+        self.map.get_index_of(path).map(|i| FileId(i as u32))
     }
 
     /// Insert `path` in `self`.
@@ -25,13 +32,9 @@ impl PathInterner {
     /// - If `path` already exists in `self`, returns its associated id;
     /// - Else, returns a newly allocated id.
     pub(crate) fn intern(&mut self, path: VfsPath) -> FileId {
-        if let Some(id) = self.get(&path) {
-            return id;
-        }
-        let id = FileId(self.vec.len() as u32);
-        self.map.insert(path.clone(), id);
-        self.vec.push(path);
-        id
+        let (id, _added) = self.map.insert_full(path);
+        assert!(id < u32::MAX as usize);
+        FileId(id as u32)
     }
 
     /// Returns the path corresponding to `id`.
@@ -40,6 +43,6 @@ impl PathInterner {
     ///
     /// Panics if `id` does not exists in `self`.
     pub(crate) fn lookup(&self, id: FileId) -> &VfsPath {
-        &self.vec[id.0 as usize]
+        self.map.get_index(id.0 as usize).unwrap()
     }
 }
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 989771ac6f..73be59a82e 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: faae991334a151d0
+lsp_ext.rs hash: e8a7502bd2b2c2f5
 
 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:
@@ -419,37 +419,23 @@ Returns internal status message, mostly for debugging purposes.
 
 Reloads project information (that is, re-executes `cargo metadata`).
 
-## Server Status
+## Status Notification
 
-**Experimental Client Capability:** `{ "serverStatus": boolean }`
+**Experimental Client Capability:** `{ "statusNotification": boolean }`
 
-**Method:** `experimental/serverStatus`
+**Method:** `rust-analyzer/status`
 
 **Notification:**
 
 ```typescript
-interface ServerStatusParams {
-    /// `ok` means that the server is completely functional.
-    ///
-    /// `warning` means that the server is partially functional.
-    /// It can server requests, but some results might be wrong due to,
-    /// for example, some missing dependencies.
-    ///
-    /// `error` means that the server is not functional. For example,
-    /// there's a fatal build configuration problem.
-    health: "ok" | "warning" | "error",
-    /// Is there any pending background work which might change the status?
-    /// For example, are dependencies being downloaded?
-    quiescent: bool,
-    /// Explanatory message to show on hover.
-    message?: string,
+interface StatusParams {
+    status: "loading" | "readyPartial" | "ready" | "invalid" | "needsReload",
 }
 ```
 
 This notification is sent from server to client.
-The client can use it to display *persistent* status to the user (in modline).
-It is similar to the `showMessage`, but is intended for stares rather than point-in-time events.
-
+The client can use it to display persistent status to the user (in modline).
+For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request.
 
 ## Syntax Tree
 
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 116f41df6e..0771ca3b69 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -159,7 +159,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
         caps.snippetTextEdit = true;
         caps.codeActionGroup = true;
         caps.hoverActions = true;
-        caps.serverStatusNotification = true;
+        caps.statusNotification = true;
         capabilities.experimental = caps;
     }
     initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index bd023f803e..c07583cfa2 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -5,7 +5,7 @@ import * as ra from './lsp_ext';
 import { Config } from './config';
 import { createClient } from './client';
 import { isRustEditor, RustEditor } from './util';
-import { ServerStatusParams } from './lsp_ext';
+import { Status } from './lsp_ext';
 
 export class Ctx {
     private constructor(
@@ -36,7 +36,7 @@ export class Ctx {
 
         res.pushCleanup(client.start());
         await client.onReady();
-        client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params));
+        client.onNotification(ra.status, (params) => res.setStatus(params.status));
         return res;
     }
 
@@ -66,28 +66,39 @@ export class Ctx {
         return this.extCtx.subscriptions;
     }
 
-    setServerStatus(status: ServerStatusParams) {
-        this.statusBar.tooltip = status.message ?? "Ready";
-        let icon = "";
-        switch (status.health) {
-            case "ok":
+    setStatus(status: Status) {
+        switch (status) {
+            case "loading":
+                this.statusBar.text = "$(sync~spin) rust-analyzer";
+                this.statusBar.tooltip = "Loading the project";
+                this.statusBar.command = undefined;
                 this.statusBar.color = undefined;
                 break;
-            case "warning":
-                this.statusBar.tooltip += "\nClick to reload.";
-                this.statusBar.command = "rust-analyzer.reloadWorkspace";
-                this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
-                icon = "$(warning) ";
+            case "readyPartial":
+                this.statusBar.text = "rust-analyzer";
+                this.statusBar.tooltip = "Ready (Partial)";
+                this.statusBar.command = undefined;
+                this.statusBar.color = undefined;
                 break;
-            case "error":
-                this.statusBar.tooltip += "\nClick to reload.";
-                this.statusBar.command = "rust-analyzer.reloadWorkspace";
+            case "ready":
+                this.statusBar.text = "rust-analyzer";
+                this.statusBar.tooltip = "Ready";
+                this.statusBar.command = undefined;
+                this.statusBar.color = undefined;
+                break;
+            case "invalid":
+                this.statusBar.text = "$(error) rust-analyzer";
+                this.statusBar.tooltip = "Failed to load the project";
+                this.statusBar.command = undefined;
                 this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
-                icon = "$(error) ";
+                break;
+            case "needsReload":
+                this.statusBar.text = "$(warning) rust-analyzer";
+                this.statusBar.tooltip = "Click to reload";
+                this.statusBar.command = "rust-analyzer.reloadWorkspace";
+                this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
                 break;
         }
-        if (!status.quiescent) icon = "$(sync~spin) ";
-        this.statusBar.text = `${icon} rust-analyzer`;
     }
 
     pushCleanup(d: Disposable) {
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index e453bb9e0b..00e128b8c3 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -10,12 +10,11 @@ export interface AnalyzerStatusParams {
 export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
 export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
 
-export interface ServerStatusParams {
-    health: "ok" | "warning" | "error";
-    quiescent: boolean;
-    message?: string;
+export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload";
+export interface StatusParams {
+    status: Status;
 }
-export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus");
+export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status");
 
 export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
 


Test patch:
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index 8d68f1b7d5..95bf26f016 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -103,7 +103,7 @@ impl<'a> Project<'a> {
                     ..Default::default()
                 }),
                 experimental: Some(json!({
-                    "serverStatusNotification": true,
+                    "statusNotification": true,
                 })),
                 ..Default::default()
             },
@@ -213,12 +213,13 @@ impl Server {
     }
     pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
         self.wait_for_message_cond(1, &|msg: &Message| match msg {
-            Message::Notification(n) if n.method == "experimental/serverStatus" => {
+            Message::Notification(n) if n.method == "rust-analyzer/status" => {
                 let status = n
                     .clone()
-                    .extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
-                    .unwrap();
-                status.quiescent
+                    .extract::<lsp_ext::StatusParams>("rust-analyzer/status")
+                    .unwrap()
+                    .status;
+                matches!(status, lsp_ext::Status::Ready)
             }
             _ => false,
         })