about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@dend.ro>2024-01-09 08:46:16 +0200
committerLaurențiu Nicola <lnicola@dend.ro>2024-01-09 08:46:16 +0200
commitf52f2f9c3ce6dc72d62b9f201fecf70c0b419234 (patch)
tree8e57feccbc0ef073b08d5f52a6fd5faa4659eb3a
parentc6a02e375be54fdf5df359356dea42f25c2bd321 (diff)
parent598511b4e8c8cad27ae400d498db8e3ae112f044 (diff)
downloadrust-f52f2f9c3ce6dc72d62b9f201fecf70c0b419234.tar.gz
rust-f52f2f9c3ce6dc72d62b9f201fecf70c0b419234.zip
Merge branch 'master' into sync-from-rust
-rw-r--r--crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs83
-rw-r--r--crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs2
-rw-r--r--crates/hir-expand/src/builtin_derive_macro.rs114
-rw-r--r--crates/hir-expand/src/builtin_fn_macro.rs20
-rw-r--r--crates/hir-expand/src/quote.rs4
-rw-r--r--crates/hir-ty/src/tests/macros.rs30
-rw-r--r--crates/ide/src/expand_macro.rs8
-rw-r--r--crates/load-cargo/src/lib.rs5
-rw-r--r--crates/rust-analyzer/src/global_state.rs83
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs25
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs1
-rw-r--r--crates/rust-analyzer/src/lsp/utils.rs35
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/mem_docs.rs5
-rw-r--r--crates/rust-analyzer/src/reload.rs7
-rw-r--r--crates/test-utils/src/minicore.rs10
-rw-r--r--crates/vfs/src/lib.rs110
17 files changed, 242 insertions, 302 deletions
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
index abd84c6a46d..553c0b79533 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
@@ -16,13 +16,12 @@ struct Foo;
 #[derive(Copy)]
 struct Foo;
 
-impl < > core::marker::Copy for Foo< > where {}"#]],
+impl < > $crate::marker::Copy for Foo< > where {}"#]],
     );
 }
 
 #[test]
 fn test_copy_expand_in_core() {
-    cov_mark::check!(test_copy_expand_in_core);
     check(
         r#"
 //- /lib.rs crate:core
@@ -41,7 +40,7 @@ macro Copy {}
 #[derive(Copy)]
 struct Foo;
 
-impl < > crate ::marker::Copy for Foo< > where {}"#]],
+impl < > $crate::marker::Copy for Foo< > where {}"#]],
     );
 }
 
@@ -57,7 +56,7 @@ struct Foo<A, B>;
 #[derive(Copy)]
 struct Foo<A, B>;
 
-impl <A: core::marker::Copy, B: core::marker::Copy, > core::marker::Copy for Foo<A, B, > where {}"#]],
+impl <A: $crate::marker::Copy, B: $crate::marker::Copy, > $crate::marker::Copy for Foo<A, B, > where {}"#]],
     );
 }
 
@@ -74,7 +73,7 @@ struct Foo<A, B, 'a, 'b>;
 #[derive(Copy)]
 struct Foo<A, B, 'a, 'b>;
 
-impl <A: core::marker::Copy, B: core::marker::Copy, > core::marker::Copy for Foo<A, B, > where {}"#]],
+impl <A: $crate::marker::Copy, B: $crate::marker::Copy, > $crate::marker::Copy for Foo<A, B, > where {}"#]],
     );
 }
 
@@ -98,7 +97,7 @@ enum Command<A, B> {
     Jump,
 }
 
-impl <A: core::clone::Clone, B: core::clone::Clone, > core::clone::Clone for Command<A, B, > where {
+impl <A: $crate::clone::Clone, B: $crate::clone::Clone, > $crate::clone::Clone for Command<A, B, > where {
     fn clone(&self ) -> Self {
         match self {
             Command::Move {
@@ -158,7 +157,7 @@ where
     generic: Vec<T::InGenericArg>,
 }
 
-impl <T: core::clone::Clone, > core::clone::Clone for Foo<T, > where T: Trait, T::InFieldShorthand: core::clone::Clone, T::InGenericArg: core::clone::Clone, {
+impl <T: $crate::clone::Clone, > $crate::clone::Clone for Foo<T, > where T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, {
     fn clone(&self ) -> Self {
         match self {
             Foo {
@@ -186,7 +185,7 @@ struct Foo<const X: usize, T>(u32);
 #[derive(Clone)]
 struct Foo<const X: usize, T>(u32);
 
-impl <const X: usize, T: core::clone::Clone, > core::clone::Clone for Foo<X, T, > where {
+impl <const X: usize, T: $crate::clone::Clone, > $crate::clone::Clone for Foo<X, T, > where {
     fn clone(&self ) -> Self {
         match self {
             Foo(f0, )=>Foo(f0.clone(), ),
@@ -226,14 +225,14 @@ enum Bar {
     Bar,
 }
 
-impl < > core::default::Default for Foo< > where {
+impl < > $crate::default::Default for Foo< > where {
     fn default() -> Self {
         Foo {
-            field1: core::default::Default::default(), field2: core::default::Default::default(),
+            field1: $crate::default::Default::default(), field2: $crate::default::Default::default(),
         }
     }
 }
-impl < > core::default::Default for Bar< > where {
+impl < > $crate::default::Default for Bar< > where {
     fn default() -> Self {
         Bar::Bar
     }
@@ -261,7 +260,7 @@ enum Command {
     Jump,
 }
 
-impl < > core::cmp::PartialEq for Command< > where {
+impl < > $crate::cmp::PartialEq for Command< > where {
     fn eq(&self , other: &Self ) -> bool {
         match (self , other) {
             (Command::Move {
@@ -274,7 +273,7 @@ impl < > core::cmp::PartialEq for Command< > where {
         }
     }
 }
-impl < > core::cmp::Eq for Command< > where {}"#]],
+impl < > $crate::cmp::Eq for Command< > where {}"#]],
     );
 }
 
@@ -299,7 +298,7 @@ enum Command {
     Jump,
 }
 
-impl < > core::cmp::PartialEq for Command< > where {
+impl < > $crate::cmp::PartialEq for Command< > where {
     fn eq(&self , other: &Self ) -> bool {
         match (self , other) {
             (Command::Move {
@@ -312,7 +311,7 @@ impl < > core::cmp::PartialEq for Command< > where {
         }
     }
 }
-impl < > core::cmp::Eq for Command< > where {}"#]],
+impl < > $crate::cmp::Eq for Command< > where {}"#]],
     );
 }
 
@@ -336,10 +335,10 @@ enum Command {
     Jump,
 }
 
-impl < > core::cmp::PartialOrd for Command< > where {
-    fn partial_cmp(&self , other: &Self ) -> core::option::Option::Option<core::cmp::Ordering> {
-        match core::intrinsics::discriminant_value(self ).partial_cmp(&core::intrinsics::discriminant_value(other)) {
-            core::option::Option::Some(core::cmp::Ordering::Equal)=> {
+impl < > $crate::cmp::PartialOrd for Command< > where {
+    fn partial_cmp(&self , other: &Self ) -> $crate::option::Option::Option<$crate::cmp::Ordering> {
+        match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) {
+            $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
                 match (self , other) {
                     (Command::Move {
                         x: x_self, y: y_self,
@@ -348,10 +347,10 @@ impl < > core::cmp::PartialOrd for Command< > where {
                         x: x_other, y: y_other,
                     }
                     )=>match x_self.partial_cmp(&x_other) {
-                        core::option::Option::Some(core::cmp::Ordering::Equal)=> {
+                        $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
                             match y_self.partial_cmp(&y_other) {
-                                core::option::Option::Some(core::cmp::Ordering::Equal)=> {
-                                    core::option::Option::Some(core::cmp::Ordering::Equal)
+                                $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
+                                    $crate::option::Option::Some($crate::cmp::Ordering::Equal)
                                 }
                                 c=>return c,
                             }
@@ -359,22 +358,22 @@ impl < > core::cmp::PartialOrd for Command< > where {
                         c=>return c,
                     }
                     , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) {
-                        core::option::Option::Some(core::cmp::Ordering::Equal)=> {
-                            core::option::Option::Some(core::cmp::Ordering::Equal)
+                        $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
+                            $crate::option::Option::Some($crate::cmp::Ordering::Equal)
                         }
                         c=>return c,
                     }
-                    , (Command::Jump, Command::Jump)=>core::option::Option::Some(core::cmp::Ordering::Equal), _unused=>core::option::Option::Some(core::cmp::Ordering::Equal)
+                    , (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal)
                 }
             }
             c=>return c,
         }
     }
 }
-impl < > core::cmp::Ord for Command< > where {
-    fn cmp(&self , other: &Self ) -> core::cmp::Ordering {
-        match core::intrinsics::discriminant_value(self ).cmp(&core::intrinsics::discriminant_value(other)) {
-            core::cmp::Ordering::Equal=> {
+impl < > $crate::cmp::Ord for Command< > where {
+    fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering {
+        match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) {
+            $crate::cmp::Ordering::Equal=> {
                 match (self , other) {
                     (Command::Move {
                         x: x_self, y: y_self,
@@ -383,10 +382,10 @@ impl < > core::cmp::Ord for Command< > where {
                         x: x_other, y: y_other,
                     }
                     )=>match x_self.cmp(&x_other) {
-                        core::cmp::Ordering::Equal=> {
+                        $crate::cmp::Ordering::Equal=> {
                             match y_self.cmp(&y_other) {
-                                core::cmp::Ordering::Equal=> {
-                                    core::cmp::Ordering::Equal
+                                $crate::cmp::Ordering::Equal=> {
+                                    $crate::cmp::Ordering::Equal
                                 }
                                 c=>return c,
                             }
@@ -394,12 +393,12 @@ impl < > core::cmp::Ord for Command< > where {
                         c=>return c,
                     }
                     , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) {
-                        core::cmp::Ordering::Equal=> {
-                            core::cmp::Ordering::Equal
+                        $crate::cmp::Ordering::Equal=> {
+                            $crate::cmp::Ordering::Equal
                         }
                         c=>return c,
                     }
-                    , (Command::Jump, Command::Jump)=>core::cmp::Ordering::Equal, _unused=>core::cmp::Ordering::Equal
+                    , (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal
                 }
             }
             c=>return c,
@@ -433,8 +432,8 @@ struct Foo {
     z: (i32, u64),
 }
 
-impl < > core::hash::Hash for Foo< > where {
-    fn hash<H: core::hash::Hasher>(&self , ra_expand_state: &mut H) {
+impl < > $crate::hash::Hash for Foo< > where {
+    fn hash<H: $crate::hash::Hasher>(&self , ra_expand_state: &mut H) {
         match self {
             Foo {
                 x: x, y: y, z: z,
@@ -471,9 +470,9 @@ enum Command {
     Jump,
 }
 
-impl < > core::hash::Hash for Command< > where {
-    fn hash<H: core::hash::Hasher>(&self , ra_expand_state: &mut H) {
-        core::mem::discriminant(self ).hash(ra_expand_state);
+impl < > $crate::hash::Hash for Command< > where {
+    fn hash<H: $crate::hash::Hasher>(&self , ra_expand_state: &mut H) {
+        $crate::mem::discriminant(self ).hash(ra_expand_state);
         match self {
             Command::Move {
                 x: x, y: y,
@@ -517,8 +516,8 @@ enum Command {
     Jump,
 }
 
-impl < > core::fmt::Debug for Command< > where {
-    fn fmt(&self , f: &mut core::fmt::Formatter) -> core::fmt::Result {
+impl < > $crate::fmt::Debug for Command< > where {
+    fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result {
         match self {
             Command::Move {
                 x: x, y: y,
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index d4798f4507d..4690ca5d363 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -136,7 +136,7 @@ fn main() { option_env!("TEST_ENV_VAR"); }
 #[rustc_builtin_macro]
 macro_rules! option_env {() => {}}
 
-fn main() { ::core::option::Option::None:: < &str>; }
+fn main() { $crate::option::Option::None:: < &str>; }
 "#]],
     );
 }
diff --git a/crates/hir-expand/src/builtin_derive_macro.rs b/crates/hir-expand/src/builtin_derive_macro.rs
index 8f240ef0732..46bbb7f92c0 100644
--- a/crates/hir-expand/src/builtin_derive_macro.rs
+++ b/crates/hir-expand/src/builtin_derive_macro.rs
@@ -1,6 +1,5 @@
 //! Builtin derives.
 
-use base_db::{CrateOrigin, LangCrateOrigin};
 use itertools::izip;
 use rustc_hash::FxHashSet;
 use span::{MacroCallId, Span};
@@ -10,6 +9,7 @@ use tracing::debug;
 use crate::{
     hygiene::span_with_def_site_ctxt,
     name::{AsName, Name},
+    quote::dollar_crate,
     span_map::SpanMapRef,
     tt,
 };
@@ -38,7 +38,7 @@ macro_rules! register_builtin {
 
                 let span = db.lookup_intern_macro_call(id).call_site;
                 let span = span_with_def_site_ctxt(db, span, id);
-                expander(db, id, span, tt, token_map)
+                expander(span, tt, token_map)
             }
 
             fn find_by_name(name: &name::Name) -> Option<Self> {
@@ -398,41 +398,13 @@ fn expand_simple_derive(
     ExpandResult::ok(expanded)
 }
 
-fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId, span: Span) -> tt::TokenTree {
-    // FIXME: make hygiene works for builtin derive macro
-    // such that $crate can be used here.
-    let cg = db.crate_graph();
-    let krate = db.lookup_intern_macro_call(id).krate;
-
-    let tt = if matches!(cg[krate].origin, CrateOrigin::Lang(LangCrateOrigin::Core)) {
-        cov_mark::hit!(test_copy_expand_in_core);
-        quote! {span => crate }
-    } else {
-        quote! {span => core }
-    };
-
-    tt.token_trees[0].clone()
-}
-
-fn copy_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = find_builtin_crate(db, id, span);
+fn copy_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::marker::Copy }, |_| quote! {span =>})
 }
 
-fn clone_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = find_builtin_crate(db, id, span);
+fn clone_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::clone::Clone }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span };
@@ -482,14 +454,8 @@ fn and_and(span: Span) -> tt::Subtree {
     quote! {span => #and& }
 }
 
-fn default_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = &find_builtin_crate(db, id, span);
+fn default_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = &dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::default::Default }, |adt| {
         let body = match &adt.shape {
             AdtShape::Struct(fields) => {
@@ -527,14 +493,8 @@ fn default_expand(
     })
 }
 
-fn debug_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = &find_builtin_crate(db, id, span);
+fn debug_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = &dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::fmt::Debug }, |adt| {
         let for_variant = |name: String, v: &VariantShape| match v {
             VariantShape::Struct(fields) => {
@@ -605,14 +565,8 @@ fn debug_expand(
     })
 }
 
-fn hash_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = &find_builtin_crate(db, id, span);
+fn hash_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = &dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::hash::Hash }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             // FIXME: Return expand error here
@@ -658,25 +612,13 @@ fn hash_expand(
     })
 }
 
-fn eq_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = find_builtin_crate(db, id, span);
+fn eq_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::Eq }, |_| quote! {span =>})
 }
 
-fn partial_eq_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = find_builtin_crate(db, id, span);
+fn partial_eq_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::PartialEq }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             // FIXME: Return expand error here
@@ -747,17 +689,11 @@ fn self_and_other_patterns(
     (self_patterns, other_patterns)
 }
 
-fn ord_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = &find_builtin_crate(db, id, span);
+fn ord_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = &dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::Ord }, |adt| {
         fn compare(
-            krate: &tt::TokenTree,
+            krate: &tt::Ident,
             left: tt::Subtree,
             right: tt::Subtree,
             rest: tt::Subtree,
@@ -811,17 +747,11 @@ fn ord_expand(
     })
 }
 
-fn partial_ord_expand(
-    db: &dyn ExpandDatabase,
-    id: MacroCallId,
-    span: Span,
-    tt: &ast::Adt,
-    tm: SpanMapRef<'_>,
-) -> ExpandResult<tt::Subtree> {
-    let krate = &find_builtin_crate(db, id, span);
+fn partial_ord_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
+    let krate = &dollar_crate(span);
     expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::PartialOrd }, |adt| {
         fn compare(
-            krate: &tt::TokenTree,
+            krate: &tt::Ident,
             left: tt::Subtree,
             right: tt::Subtree,
             rest: tt::Subtree,
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index f99a8917623..65a55f8b5b8 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -6,16 +6,14 @@ use either::Either;
 use itertools::Itertools;
 use mbe::{parse_exprs_with_sep, parse_to_token_tree};
 use span::{Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID};
-use syntax::{
-    ast::{self, AstToken},
-    SmolStr,
-};
+use syntax::ast::{self, AstToken};
 
 use crate::{
     db::ExpandDatabase,
     hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt},
     name::{self, known},
     quote,
+    quote::dollar_crate,
     tt::{self, DelimSpan},
     ExpandError, ExpandResult, HirFileIdExt, MacroCallId,
 };
@@ -205,7 +203,7 @@ fn assert_expand(
 ) -> ExpandResult<tt::Subtree> {
     let call_site_span = span_with_call_site_ctxt(db, span, id);
     let args = parse_exprs_with_sep(tt, ',', call_site_span);
-    let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span };
+    let dollar_crate = dollar_crate(span);
     let expanded = match &*args {
         [cond, panic_args @ ..] => {
             let comma = tt::Subtree {
@@ -300,7 +298,7 @@ fn asm_expand(
             [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))]
             | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] =>
             {
-                let dollar_krate = tt::Ident { text: SmolStr::new_inline("$crate"), span };
+                let dollar_krate = dollar_crate(span);
                 literals.push(quote!(span=>#dollar_krate::format_args!(#lit);));
             }
             _ => break,
@@ -345,7 +343,7 @@ fn panic_expand(
     tt: &tt::Subtree,
     span: Span,
 ) -> ExpandResult<tt::Subtree> {
-    let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span };
+    let dollar_crate = dollar_crate(span);
     let call_site_span = span_with_call_site_ctxt(db, span, id);
 
     let mac =
@@ -371,7 +369,7 @@ fn unreachable_expand(
     tt: &tt::Subtree,
     span: Span,
 ) -> ExpandResult<tt::Subtree> {
-    let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span };
+    let dollar_crate = dollar_crate(span);
     let call_site_span = span_with_call_site_ctxt(db, span, id);
 
     let mac = if use_panic_2021(db, call_site_span) {
@@ -763,10 +761,10 @@ fn option_env_expand(
             return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e)
         }
     };
-    // FIXME: Use `DOLLAR_CRATE` when that works in eager macros.
+    let dollar_crate = dollar_crate(span);
     let expanded = match get_env_inner(db, arg_id, &key) {
-        None => quote! {span => ::core::option::Option::None::<&str> },
-        Some(s) => quote! {span => ::core::option::Option::Some(#s) },
+        None => quote! {span => #dollar_crate::option::Option::None::<&str> },
+        Some(s) => quote! {span => #dollar_crate::option::Option::Some(#s) },
     };
 
     ExpandResult::ok(expanded)
diff --git a/crates/hir-expand/src/quote.rs b/crates/hir-expand/src/quote.rs
index 9bdd75f9d22..a3b84afd2ae 100644
--- a/crates/hir-expand/src/quote.rs
+++ b/crates/hir-expand/src/quote.rs
@@ -4,6 +4,10 @@ use span::Span;
 
 use crate::name::Name;
 
+pub(crate) fn dollar_crate(span: Span) -> tt::Ident<Span> {
+    tt::Ident { text: syntax::SmolStr::new_inline("$crate"), span }
+}
+
 // A helper macro quote macro
 // FIXME:
 // 1. Not all puncts are handled
diff --git a/crates/hir-ty/src/tests/macros.rs b/crates/hir-ty/src/tests/macros.rs
index d16e0eb0137..622b4f56d4f 100644
--- a/crates/hir-ty/src/tests/macros.rs
+++ b/crates/hir-ty/src/tests/macros.rs
@@ -987,15 +987,12 @@ fn infer_builtin_macros_env() {
 fn infer_builtin_macros_option_env() {
     check_types(
         r#"
-        //- minicore: option
-        //- /main.rs env:foo=bar
-        #[rustc_builtin_macro]
-        macro_rules! option_env {() => {}}
-
-        fn main() {
-            let x = option_env!("foo");
-              //^ Option<&str>
-        }
+//- minicore: env
+//- /main.rs env:foo=bar
+fn main() {
+    let x = option_env!("foo");
+      //^ Option<&str>
+}
         "#,
     );
 }
@@ -1015,6 +1012,21 @@ fn test() {
 }
 
 #[test]
+fn infer_builtin_derive_resolves_with_core_module() {
+    check_types(
+        r#"
+//- minicore: derive, clone
+mod core {}
+#[derive(Clone)]
+struct S;
+fn test() {
+    S.clone();
+} //^^^^^^^^^ S
+"#,
+    );
+}
+
+#[test]
 fn infer_derive_clone_with_params() {
     check_types(
         r#"
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 024053effe4..17c701ad035 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -481,7 +481,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Clone
-                impl < >core::clone::Clone for Foo< >where {
+                impl < >$crate::clone::Clone for Foo< >where {
                   fn clone(&self) -> Self {
                     match self {
                       Foo{}
@@ -507,7 +507,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy
-                impl < >core::marker::Copy for Foo< >where{}"#]],
+                impl < >$crate::marker::Copy for Foo< >where{}"#]],
         );
     }
 
@@ -522,7 +522,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy
-                impl < >core::marker::Copy for Foo< >where{}"#]],
+                impl < >$crate::marker::Copy for Foo< >where{}"#]],
         );
         check(
             r#"
@@ -533,7 +533,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Clone
-                impl < >core::clone::Clone for Foo< >where {
+                impl < >$crate::clone::Clone for Foo< >where {
                   fn clone(&self) -> Self {
                     match self {
                       Foo{}
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 556ed73a04c..ee0b9e6d504 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -331,9 +331,8 @@ fn load_crate_graph(
     }
     let changes = vfs.take_changes();
     for file in changes {
-        if file.exists() {
-            let contents = vfs.file_contents(file.file_id);
-            if let Ok(text) = std::str::from_utf8(contents) {
+        if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
+            if let Ok(text) = std::str::from_utf8(&v) {
                 analysis_change.change_file(file.file_id, Some(text.into()))
             }
         }
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index f57a27305f0..da132f93968 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::time::Instant;
+use std::{collections::hash_map::Entry, time::Instant};
 
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
@@ -21,7 +21,7 @@ use proc_macro_api::ProcMacroServer;
 use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
 use rustc_hash::{FxHashMap, FxHashSet};
 use triomphe::Arc;
-use vfs::{AnchoredPathBuf, Vfs};
+use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
 
 use crate::{
     config::{Config, ConfigError},
@@ -217,8 +217,8 @@ impl GlobalState {
     pub(crate) fn process_changes(&mut self) -> bool {
         let _p = profile::span("GlobalState::process_changes");
 
-        let mut file_changes = FxHashMap::default();
-        let (change, changed_files, workspace_structure_change) = {
+        let mut file_changes = FxHashMap::<_, (bool, ChangedFile)>::default();
+        let (change, modified_files, workspace_structure_change) = {
             let mut change = Change::new();
             let mut guard = self.vfs.write();
             let changed_files = guard.0.take_changes();
@@ -233,64 +233,63 @@ impl GlobalState {
             // 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::ChangeKind::*;
-
-                file_changes
-                    .entry(changed_file.file_id)
-                    .and_modify(|(change, just_created)| {
-                        // None -> Delete => keep
-                        // Create -> Delete => collapse
-                        //
-                        match (change, just_created, changed_file.change_kind) {
+                use vfs::Change::*;
+                match file_changes.entry(changed_file.file_id) {
+                    Entry::Occupied(mut o) => {
+                        let (just_created, change) = o.get_mut();
+                        match (&mut change.change, just_created, changed_file.change) {
                             // latter `Delete` wins
                             (change, _, Delete) => *change = Delete,
                             // merge `Create` with `Create` or `Modify`
-                            (Create, _, Create | Modify) => {}
+                            (Create(prev), _, Create(new) | Modify(new)) => *prev = new,
                             // collapse identical `Modify`es
-                            (Modify, _, Modify) => {}
+                            (Modify(prev), _, Modify(new)) => *prev = new,
                             // equivalent to `Modify`
-                            (change @ Delete, just_created, Create) => {
-                                *change = Modify;
+                            (change @ Delete, just_created, Create(new)) => {
+                                *change = Modify(new);
                                 *just_created = true;
                             }
                             // shouldn't occur, but collapse into `Create`
-                            (change @ Delete, just_created, Modify) => {
-                                *change = Create;
+                            (change @ Delete, just_created, Modify(new)) => {
+                                *change = Create(new);
                                 *just_created = true;
                             }
                             // shouldn't occur, but collapse into `Modify`
-                            (Modify, _, Create) => {}
+                            (Modify(prev), _, Create(new)) => *prev = new,
                         }
-                    })
-                    .or_insert((
-                        changed_file.change_kind,
-                        matches!(changed_file.change_kind, Create),
-                    ));
+                    }
+                    Entry::Vacant(v) => {
+                        _ = v.insert((matches!(&changed_file.change, Create(_)), changed_file))
+                    }
+                }
             }
 
             let changed_files: Vec<_> = file_changes
                 .into_iter()
-                .filter(|(_, (change_kind, just_created))| {
-                    !matches!((change_kind, just_created), (vfs::ChangeKind::Delete, true))
+                .filter(|(_, (just_created, change))| {
+                    !(*just_created && matches!(change.change, vfs::Change::Delete))
                 })
-                .map(|(file_id, (change_kind, _))| vfs::ChangedFile { file_id, change_kind })
+                .map(|(file_id, (_, change))| vfs::ChangedFile { file_id, ..change })
                 .collect();
 
             let mut workspace_structure_change = None;
             // A file was added or deleted
             let mut has_structure_changes = false;
             let mut bytes = vec![];
-            for file in &changed_files {
+            let mut modified_files = vec![];
+            for file in changed_files {
                 let vfs_path = &vfs.file_path(file.file_id);
                 if let Some(path) = vfs_path.as_path() {
                     let path = path.to_path_buf();
-                    if reload::should_refresh_for_change(&path, file.change_kind) {
+                    if reload::should_refresh_for_change(&path, file.kind()) {
                         workspace_structure_change = Some((path.clone(), false));
                     }
                     if file.is_created_or_deleted() {
                         has_structure_changes = true;
                         workspace_structure_change =
                             Some((path, self.crate_graph_file_dependencies.contains(vfs_path)));
+                    } else {
+                        modified_files.push(file.file_id);
                     }
                 }
 
@@ -299,10 +298,8 @@ impl GlobalState {
                     self.diagnostics.clear_native_for(file.file_id);
                 }
 
-                let text = if file.exists() {
-                    let bytes = vfs.file_contents(file.file_id).to_vec();
-
-                    String::from_utf8(bytes).ok().and_then(|text| {
+                let text = if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
+                    String::from_utf8(v).ok().and_then(|text| {
                         // FIXME: Consider doing normalization in the `vfs` instead? That allows
                         // getting rid of some locking
                         let (text, line_endings) = LineEndings::normalize(text);
@@ -327,11 +324,10 @@ impl GlobalState {
                 let roots = self.source_root_config.partition(vfs);
                 change.set_roots(roots);
             }
-            (change, changed_files, workspace_structure_change)
+            (change, modified_files, workspace_structure_change)
         };
 
         self.analysis_host.apply_change(change);
-
         {
             let raw_database = self.analysis_host.raw_database();
             // FIXME: ideally we should only trigger a workspace fetch for non-library changes
@@ -343,13 +339,12 @@ impl GlobalState {
                     force_crate_graph_reload,
                 );
             }
-            self.proc_macro_changed =
-                changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
-                    let crates = raw_database.relevant_crates(file.file_id);
-                    let crate_graph = raw_database.crate_graph();
+            self.proc_macro_changed = modified_files.into_iter().any(|file_id| {
+                let crates = raw_database.relevant_crates(file_id);
+                let crate_graph = raw_database.crate_graph();
 
-                    crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
-                });
+                crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
+            });
         }
 
         true
@@ -494,10 +489,6 @@ impl GlobalStateSnapshot {
         })
     }
 
-    pub(crate) fn vfs_memory_usage(&self) -> usize {
-        self.vfs_read().memory_usage()
-    }
-
     pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
         self.vfs.read().0.exists(file_id)
     }
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 7e6219991b6..c0d35498c66 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -59,7 +59,13 @@ pub(crate) fn handle_did_open_text_document(
     if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
         let already_exists = state
             .mem_docs
-            .insert(path.clone(), DocumentData::new(params.text_document.version))
+            .insert(
+                path.clone(),
+                DocumentData::new(
+                    params.text_document.version,
+                    params.text_document.text.clone().into_bytes(),
+                ),
+            )
             .is_err();
         if already_exists {
             tracing::error!("duplicate DidOpenTextDocument: {}", path);
@@ -76,11 +82,12 @@ pub(crate) fn handle_did_change_text_document(
     let _p = profile::span("handle_did_change_text_document");
 
     if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
-        match state.mem_docs.get_mut(&path) {
+        let data = match state.mem_docs.get_mut(&path) {
             Some(doc) => {
                 // The version passed in DidChangeTextDocument is the version after all edits are applied
                 // so we should apply it before the vfs is notified.
                 doc.version = params.text_document.version;
+                &mut doc.data
             }
             None => {
                 tracing::error!("unexpected DidChangeTextDocument: {}", path);
@@ -88,16 +95,14 @@ pub(crate) fn handle_did_change_text_document(
             }
         };
 
-        let text = apply_document_changes(
+        let new_contents = apply_document_changes(
             state.config.position_encoding(),
-            || {
-                let vfs = &state.vfs.read().0;
-                let file_id = vfs.file_id(&path).unwrap();
-                std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into()
-            },
+            std::str::from_utf8(data).unwrap(),
             params.content_changes,
-        );
-        state.vfs.write().0.set_file_contents(path, Some(text.into_bytes()));
+        )
+        .into_bytes();
+        *data = new_contents.clone();
+        state.vfs.write().0.set_file_contents(path, Some(new_contents));
     }
     Ok(())
 }
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 13544558c50..22c7e9b0503 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -103,7 +103,6 @@ pub(crate) fn handle_analyzer_status(
                 .collect::<Vec<&AbsPath>>()
         );
     }
-    format_to!(buf, "\nVfs memory usage: {}\n", profile::Bytes::new(snap.vfs_memory_usage() as _));
     buf.push_str("\nAnalysis:\n");
     buf.push_str(
         &snap
diff --git a/crates/rust-analyzer/src/lsp/utils.rs b/crates/rust-analyzer/src/lsp/utils.rs
index a4417e4d4a1..fa5ea5b57db 100644
--- a/crates/rust-analyzer/src/lsp/utils.rs
+++ b/crates/rust-analyzer/src/lsp/utils.rs
@@ -168,7 +168,7 @@ impl GlobalState {
 
 pub(crate) fn apply_document_changes(
     encoding: PositionEncoding,
-    file_contents: impl FnOnce() -> String,
+    file_contents: &str,
     mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
 ) -> String {
     // If at least one of the changes is a full document change, use the last
@@ -179,7 +179,7 @@ pub(crate) fn apply_document_changes(
                 let text = mem::take(&mut content_changes[idx].text);
                 (text, &content_changes[idx + 1..])
             }
-            None => (file_contents(), &content_changes[..]),
+            None => (file_contents.to_owned(), &content_changes[..]),
         };
     if content_changes.is_empty() {
         return text;
@@ -276,11 +276,11 @@ mod tests {
         }
 
         let encoding = PositionEncoding::Wide(WideEncoding::Utf16);
-        let text = apply_document_changes(encoding, || String::new(), vec![]);
+        let text = apply_document_changes(encoding, "", vec![]);
         assert_eq!(text, "");
         let text = apply_document_changes(
             encoding,
-            || text,
+            &text,
             vec![TextDocumentContentChangeEvent {
                 range: None,
                 range_length: None,
@@ -288,49 +288,46 @@ mod tests {
             }],
         );
         assert_eq!(text, "the");
-        let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]);
+        let text = apply_document_changes(encoding, &text, c![0, 3; 0, 3 => " quick"]);
         assert_eq!(text, "the quick");
         let text =
-            apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
+            apply_document_changes(encoding, &text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
         assert_eq!(text, "quick foxes");
-        let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]);
+        let text = apply_document_changes(encoding, &text, c![0, 11; 0, 11 => "\ndream"]);
         assert_eq!(text, "quick foxes\ndream");
-        let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]);
+        let text = apply_document_changes(encoding, &text, c![1, 0; 1, 0 => "have "]);
         assert_eq!(text, "quick foxes\nhave dream");
         let text = apply_document_changes(
             encoding,
-            || text,
+            &text,
             c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
         );
         assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
-        let text = apply_document_changes(
-            encoding,
-            || text,
-            c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"],
-        );
+        let text =
+            apply_document_changes(encoding, &text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
         assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
         let text = apply_document_changes(
             encoding,
-            || text,
+            &text,
             c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
         );
         assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
         let text =
-            apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
+            apply_document_changes(encoding, &text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
         assert_eq!(text, "the quick \nthey have quiet dreams\n");
 
         let text = String::from("❤️");
-        let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]);
+        let text = apply_document_changes(encoding, &text, c![0, 0; 0, 0 => "a"]);
         assert_eq!(text, "a❤️");
 
         let text = String::from("a\nb");
         let text =
-            apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
+            apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
         assert_eq!(text, "adcb");
 
         let text = String::from("a\nb");
         let text =
-            apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
+            apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
         assert_eq!(text, "ațc\ncb");
     }
 
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index cdf41c955d2..356c332f90d 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -576,6 +576,8 @@ impl GlobalState {
                 let vfs = &mut self.vfs.write().0;
                 for (path, contents) in files {
                     let path = VfsPath::from(path);
+                    // if the file is in mem docs, it's managed by the client via notifications
+                    // so only set it if its not in there
                     if !self.mem_docs.contains(&path) {
                         vfs.set_file_contents(path, contents);
                     }
diff --git a/crates/rust-analyzer/src/mem_docs.rs b/crates/rust-analyzer/src/mem_docs.rs
index 45a1dab9772..6a93a0ebb4c 100644
--- a/crates/rust-analyzer/src/mem_docs.rs
+++ b/crates/rust-analyzer/src/mem_docs.rs
@@ -62,10 +62,11 @@ impl MemDocs {
 #[derive(Debug, Clone)]
 pub(crate) struct DocumentData {
     pub(crate) version: i32,
+    pub(crate) data: Vec<u8>,
 }
 
 impl DocumentData {
-    pub(crate) fn new(version: i32) -> Self {
-        DocumentData { version }
+    pub(crate) fn new(version: i32, data: Vec<u8>) -> Self {
+        DocumentData { version, data }
     }
 }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 91dc6c2e4b4..8e3fa7fa4da 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -503,10 +503,9 @@ impl GlobalState {
                 match vfs.file_id(&vfs_path) {
                     Some(file_id) => Some(file_id),
                     None => {
-                        if !self.mem_docs.contains(&vfs_path) {
-                            let contents = loader.handle.load_sync(path);
-                            vfs.set_file_contents(vfs_path.clone(), contents);
-                        }
+                        // FIXME: Consider not loading this here?
+                        let contents = loader.handle.load_sync(path);
+                        vfs.set_file_contents(vfs_path.clone(), contents);
                         vfs.file_id(&vfs_path)
                     }
                 }
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 1f3136404c6..140bb080427 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -25,6 +25,7 @@
 //!     derive:
 //!     discriminant:
 //!     drop:
+//!     env: option
 //!     eq: sized
 //!     error: fmt
 //!     fmt: result, transmute, coerce_unsized
@@ -1450,6 +1451,15 @@ mod macros {
     #[macro_export]
     macro_rules! concat {}
     // endregion:concat
+
+    // region:env
+    #[rustc_builtin_macro]
+    #[macro_export]
+    macro_rules! env {}
+    #[rustc_builtin_macro]
+    #[macro_export]
+    macro_rules! option_env {}
+    // endregion:env
 }
 
 // region:non_zero
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
index ef5b10ee9db..34a85818eb8 100644
--- a/crates/vfs/src/lib.rs
+++ b/crates/vfs/src/lib.rs
@@ -1,8 +1,8 @@
 //! # Virtual File System
 //!
-//! VFS stores all files read by rust-analyzer. Reading file contents from VFS
-//! always returns the same contents, unless VFS was explicitly modified with
-//! [`set_file_contents`]. All changes to VFS are logged, and can be retrieved via
+//! VFS records all file changes pushed to it via [`set_file_contents`].
+//! As such it only ever stores changes, not the actual content of a file at any given moment.
+//! All file changes are logged, and can be retrieved via
 //! [`take_changes`] method. The pack of changes is then pushed to `salsa` and
 //! triggers incremental recomputation.
 //!
@@ -84,40 +84,65 @@ impl FileId {
 /// safe because `FileId` is a newtype of `u32`
 impl nohash_hasher::IsEnabled for FileId {}
 
-/// Storage for all files read by rust-analyzer.
+/// Storage for all file changes and the file id to path mapping.
 ///
 /// For more information see the [crate-level](crate) documentation.
 #[derive(Default)]
 pub struct Vfs {
     interner: PathInterner,
-    data: Vec<Option<Vec<u8>>>,
+    data: Vec<FileState>,
     changes: Vec<ChangedFile>,
 }
 
+#[derive(Copy, PartialEq, PartialOrd, Clone)]
+pub enum FileState {
+    Exists,
+    Deleted,
+}
+
 /// Changed file in the [`Vfs`].
 #[derive(Debug)]
 pub struct ChangedFile {
     /// Id of the changed file
     pub file_id: FileId,
     /// Kind of change
-    pub change_kind: ChangeKind,
+    pub change: Change,
 }
 
 impl ChangedFile {
     /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
     pub fn exists(&self) -> bool {
-        self.change_kind != ChangeKind::Delete
+        !matches!(self.change, Change::Delete)
     }
 
     /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
-    /// [`Delete`](ChangeKind::Delete).
+    /// [`Delete`](Change::Delete).
     pub fn is_created_or_deleted(&self) -> bool {
-        matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
+        matches!(self.change, Change::Create(_) | Change::Delete)
     }
+
+    pub fn kind(&self) -> ChangeKind {
+        match self.change {
+            Change::Create(_) => ChangeKind::Create,
+            Change::Modify(_) => ChangeKind::Modify,
+            Change::Delete => ChangeKind::Delete,
+        }
+    }
+}
+
+/// Kind of [file change](ChangedFile).
+#[derive(Eq, PartialEq, Debug)]
+pub enum Change {
+    /// The file was (re-)created
+    Create(Vec<u8>),
+    /// The file was modified
+    Modify(Vec<u8>),
+    /// The file was deleted
+    Delete,
 }
 
 /// Kind of [file change](ChangedFile).
-#[derive(Eq, PartialEq, Copy, Clone, Debug)]
+#[derive(Eq, PartialEq, Debug)]
 pub enum ChangeKind {
     /// The file was (re-)created
     Create,
@@ -130,7 +155,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| self.get(it).is_some())
+        self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists))
     }
 
     /// File path corresponding to the given `file_id`.
@@ -142,28 +167,13 @@ impl Vfs {
         self.interner.lookup(file_id).clone()
     }
 
-    /// File content corresponding to the given `file_id`.
-    ///
-    /// # Panics
-    ///
-    /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
-    /// deleted.
-    pub fn file_contents(&self, file_id: FileId) -> &[u8] {
-        self.get(file_id).as_deref().unwrap()
-    }
-
-    /// Returns the overall memory usage for the stored files.
-    pub fn memory_usage(&self) -> usize {
-        self.data.iter().flatten().map(|d| d.capacity()).sum()
-    }
-
     /// Returns an iterator over the stored ids and their corresponding paths.
     ///
     /// This will skip deleted files.
     pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
         (0..self.data.len())
             .map(|it| FileId(it as u32))
-            .filter(move |&file_id| self.get(file_id).is_some())
+            .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)
@@ -176,28 +186,21 @@ impl Vfs {
     ///
     /// If the path does not currently exists in the `Vfs`, allocates a new
     /// [`FileId`] for it.
-    pub fn set_file_contents(&mut self, path: VfsPath, mut contents: Option<Vec<u8>>) -> bool {
+    pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
         let file_id = self.alloc_file_id(path);
-        let change_kind = match (self.get(file_id), &contents) {
-            (None, None) => return false,
-            (Some(old), Some(new)) if old == new => return false,
-            (None, Some(_)) => ChangeKind::Create,
-            (Some(_), None) => ChangeKind::Delete,
-            (Some(_), Some(_)) => ChangeKind::Modify,
+        let change_kind = match (self.get(file_id), contents) {
+            (FileState::Deleted, None) => return false,
+            (FileState::Deleted, Some(v)) => Change::Create(v),
+            (FileState::Exists, None) => Change::Delete,
+            (FileState::Exists, Some(v)) => Change::Modify(v),
         };
-        if let Some(contents) = &mut contents {
-            contents.shrink_to_fit();
-        }
-        *self.get_mut(file_id) = contents;
-        self.changes.push(ChangedFile { file_id, change_kind });
+        let changed_file = ChangedFile { file_id, change: change_kind };
+        self.data[file_id.0 as usize] =
+            if changed_file.exists() { FileState::Exists } else { FileState::Deleted };
+        self.changes.push(changed_file);
         true
     }
 
-    /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
-    pub fn has_changes(&self) -> bool {
-        !self.changes.is_empty()
-    }
-
     /// Drain and returns all the changes in the `Vfs`.
     pub fn take_changes(&mut self) -> Vec<ChangedFile> {
         mem::take(&mut self.changes)
@@ -205,7 +208,7 @@ impl Vfs {
 
     /// Provides a panic-less way to verify file_id validity.
     pub fn exists(&self, file_id: FileId) -> bool {
-        self.get(file_id).is_some()
+        matches!(self.get(file_id), FileState::Exists)
     }
 
     /// Returns the id associated with `path`
@@ -219,26 +222,17 @@ impl Vfs {
         let file_id = self.interner.intern(path);
         let idx = file_id.0 as usize;
         let len = self.data.len().max(idx + 1);
-        self.data.resize_with(len, || None);
+        self.data.resize(len, FileState::Deleted);
         file_id
     }
 
-    /// Returns the content associated with the given `file_id`.
-    ///
-    /// # Panics
-    ///
-    /// Panics if no file is associated to that id.
-    fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
-        &self.data[file_id.0 as usize]
-    }
-
-    /// Mutably returns the content associated with the given `file_id`.
+    /// Returns the status of the file associated with the given `file_id`.
     ///
     /// # Panics
     ///
     /// Panics if no file is associated to that id.
-    fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
-        &mut self.data[file_id.0 as usize]
+    fn get(&self, file_id: FileId) -> FileState {
+        self.data[file_id.0 as usize]
     }
 }