about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir-def/src/item_tree.rs5
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe.rs35
-rw-r--r--crates/hir-def/src/macro_expansion_tests/proc_macros.rs7
-rw-r--r--crates/hir-ty/Cargo.toml1
-rw-r--r--crates/hir-ty/src/chalk_db.rs2
-rw-r--r--crates/hir-ty/src/chalk_ext.rs10
-rw-r--r--crates/hir-ty/src/display.rs15
-rw-r--r--crates/hir-ty/src/infer.rs9
-rw-r--r--crates/hir-ty/src/layout.rs19
-rw-r--r--crates/hir-ty/src/layout/tests.rs115
-rw-r--r--crates/hir-ty/src/lib.rs9
-rw-r--r--crates/hir-ty/src/lower.rs108
-rw-r--r--crates/ide-assists/src/handlers/add_braces.rs155
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs25
-rw-r--r--crates/ide/src/inlay_hints/adjustment.rs22
-rw-r--r--crates/mbe/src/syntax_bridge.rs40
-rw-r--r--crates/mbe/src/to_parser_input.rs7
-rw-r--r--crates/mbe/src/tt_iter.rs16
-rw-r--r--crates/parser/src/event.rs13
-rw-r--r--crates/parser/src/grammar/expressions.rs98
-rw-r--r--crates/parser/src/lib.rs4
-rw-r--r--crates/parser/src/output.rs61
-rw-r--r--crates/parser/src/parser.rs29
-rw-r--r--crates/parser/src/shortcuts.rs53
-rw-r--r--crates/parser/src/tests/prefix_entries.rs4
-rw-r--r--crates/parser/test_data/parser/inline/ok/0011_field_expr.rast33
-rw-r--r--crates/parser/test_data/parser/inline/ok/0011_field_expr.rs2
-rw-r--r--crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast43
-rw-r--r--crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs2
-rw-r--r--crates/parser/test_data/parser/inline/ok/0137_await_expr.rast35
-rw-r--r--crates/parser/test_data/parser/inline/ok/0137_await_expr.rs2
-rw-r--r--crates/project-model/src/cargo_workspace.rs1
-rw-r--r--crates/project-model/src/lib.rs2
-rw-r--r--crates/project-model/src/sysroot.rs11
-rw-r--r--crates/project-model/src/target_data_layout.rs2
-rw-r--r--crates/project-model/src/workspace.rs41
-rw-r--r--crates/rust-analyzer/src/config.rs8
-rw-r--r--crates/tt/src/buffer.rs21
-rw-r--r--docs/user/generated_config.adoc8
-rw-r--r--editors/code/language-configuration.json4
-rw-r--r--editors/code/package.json8
43 files changed, 930 insertions, 158 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c1f146411b2..9cc34a876dc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -583,6 +583,7 @@ dependencies = [
  "limit",
  "once_cell",
  "profile",
+ "project-model",
  "rustc-hash",
  "scoped-tls",
  "smallvec",
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 3e1f7d44460..19d01630ef0 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -111,7 +111,8 @@ impl ItemTree {
             Some(node) => node,
             None => return Default::default(),
         };
-        if never!(syntax.kind() == SyntaxKind::ERROR) {
+        if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax)
+        {
             // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic
             return Default::default();
         }
@@ -133,7 +134,7 @@ impl ItemTree {
                     ctx.lower_macro_stmts(stmts)
                 },
                 _ => {
-                    panic!("cannot create item tree from {syntax:?} {syntax}");
+                    panic!("cannot create item tree for file {file_id:?} from {syntax:?} {syntax}");
                 },
             }
         };
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index 2d5f2a692e5..49bbc64bff1 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -97,6 +97,41 @@ fn#19 main#20(#21)#21 {#22
 "##]],
     );
 }
+#[test]
+fn float_field_acces_macro_input() {
+    check(
+        r#"
+macro_rules! foo {
+    ($expr:expr) => {
+        fn foo() {
+            $expr;
+        }
+    };
+}
+foo!(x .0.1);
+foo!(x .2. 3);
+foo!(x .4 .5);
+"#,
+        expect![[r#"
+macro_rules! foo {
+    ($expr:expr) => {
+        fn foo() {
+            $expr;
+        }
+    };
+}
+fn foo() {
+    (x.0.1);
+}
+fn foo() {
+    (x.2.3);
+}
+fn foo() {
+    (x.4.5);
+}
+"#]],
+    );
+}
 
 #[test]
 fn mbe_smoke_test() {
diff --git a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
index 118c14ed843..822bdcc122d 100644
--- a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
+++ b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
@@ -104,7 +104,7 @@ macro_rules! id {
         $($t)*
     };
 }
-id /*+errors*/! {
+id! {
     #[proc_macros::identity]
     impl Foo for WrapBj {
         async fn foo(&self) {
@@ -113,18 +113,17 @@ id /*+errors*/! {
     }
 }
 "#,
-        expect![[r##"
+        expect![[r#"
 macro_rules! id {
     ($($t:tt)*) => {
         $($t)*
     };
 }
-/* parse error: expected SEMICOLON */
 #[proc_macros::identity] impl Foo for WrapBj {
     async fn foo(&self ) {
         self .0.id().await ;
     }
 }
-"##]],
+"#]],
     );
 }
diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml
index 8b762bf829b..490bbe1e724 100644
--- a/crates/hir-ty/Cargo.toml
+++ b/crates/hir-ty/Cargo.toml
@@ -48,6 +48,7 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features =
     "registry",
 ] }
 tracing-tree = "0.2.1"
+project-model = { path = "../project-model" }
 
 # local deps
 test-utils.workspace = true
diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs
index bbb6625855d..6989e9fb9be 100644
--- a/crates/hir-ty/src/chalk_db.rs
+++ b/crates/hir-ty/src/chalk_db.rs
@@ -205,7 +205,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
                     .return_type_impl_traits(func)
                     .expect("impl trait id without impl traits");
                 let (datas, binders) = (*datas).as_ref().into_value_and_skipped_binders();
-                let data = &datas.impl_traits[idx as usize];
+                let data = &datas.impl_traits[idx];
                 let bound = OpaqueTyDatumBound {
                     bounds: make_single_type_binders(data.bounds.skip_binders().to_vec()),
                     where_clauses: chalk_ir::Binders::empty(Interner, vec![]),
diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs
index 329c87c74e9..45c975dfcdc 100644
--- a/crates/hir-ty/src/chalk_ext.rs
+++ b/crates/hir-ty/src/chalk_ext.rs
@@ -234,9 +234,8 @@ impl TyExt for Ty {
                     }
                     ImplTraitId::ReturnTypeImplTrait(func, idx) => {
                         db.return_type_impl_traits(func).map(|it| {
-                            let data = (*it)
-                                .as_ref()
-                                .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+                            let data =
+                                (*it).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone());
                             data.substitute(Interner, &subst).into_value_and_skipped_binders().0
                         })
                     }
@@ -247,9 +246,8 @@ impl TyExt for Ty {
                 {
                     ImplTraitId::ReturnTypeImplTrait(func, idx) => {
                         db.return_type_impl_traits(func).map(|it| {
-                            let data = (*it)
-                                .as_ref()
-                                .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+                            let data =
+                                (*it).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone());
                             data.substitute(Interner, &opaque_ty.substitution)
                         })
                     }
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 1d4bf9ecb74..462c9b45759 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -458,9 +458,8 @@ impl HirDisplay for Ty {
                             let datas = db
                                 .return_type_impl_traits(func)
                                 .expect("impl trait id without data");
-                            let data = (*datas)
-                                .as_ref()
-                                .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+                            let data =
+                                (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone());
                             let bounds = data.substitute(Interner, parameters);
                             let mut len = bounds.skip_binders().len();
 
@@ -718,9 +717,8 @@ impl HirDisplay for Ty {
                     ImplTraitId::ReturnTypeImplTrait(func, idx) => {
                         let datas =
                             db.return_type_impl_traits(func).expect("impl trait id without data");
-                        let data = (*datas)
-                            .as_ref()
-                            .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+                        let data =
+                            (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone());
                         let bounds = data.substitute(Interner, &parameters);
                         let krate = func.lookup(db.upcast()).module(db.upcast()).krate();
                         write_bounds_like_dyn_trait_with_prefix(
@@ -850,9 +848,8 @@ impl HirDisplay for Ty {
                     ImplTraitId::ReturnTypeImplTrait(func, idx) => {
                         let datas =
                             db.return_type_impl_traits(func).expect("impl trait id without data");
-                        let data = (*datas)
-                            .as_ref()
-                            .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+                        let data =
+                            (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone());
                         let bounds = data.substitute(Interner, &opaque_ty.substitution);
                         let krate = func.lookup(db.upcast()).module(db.upcast()).krate();
                         write_bounds_like_dyn_trait_with_prefix(
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 571b3e96863..a76d33c0cde 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -39,7 +39,7 @@ use stdx::always;
 use crate::{
     db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany,
     lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Const, DomainGoal,
-    GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, Substitution,
+    GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, RpitId, Substitution,
     TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind,
 };
 
@@ -352,6 +352,7 @@ pub struct InferenceResult {
     /// **Note**: When a pattern type is resolved it may still contain
     /// unresolved or missing subpatterns or subpatterns of mismatched types.
     pub type_of_pat: ArenaMap<PatId, Ty>,
+    pub type_of_rpit: ArenaMap<RpitId, Ty>,
     type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
     /// Interned common types to return references to.
     standard_types: InternedStandardTypes,
@@ -525,6 +526,9 @@ impl<'a> InferenceContext<'a> {
         for ty in result.type_of_pat.values_mut() {
             *ty = table.resolve_completely(ty.clone());
         }
+        for ty in result.type_of_rpit.iter_mut().map(|x| x.1) {
+            *ty = table.resolve_completely(ty.clone());
+        }
         for mismatch in result.type_mismatches.values_mut() {
             mismatch.expected = table.resolve_completely(mismatch.expected.clone());
             mismatch.actual = table.resolve_completely(mismatch.actual.clone());
@@ -603,7 +607,7 @@ impl<'a> InferenceContext<'a> {
                         _ => unreachable!(),
                     };
                     let bounds = (*rpits).map_ref(|rpits| {
-                        rpits.impl_traits[idx as usize].bounds.map_ref(|it| it.into_iter())
+                        rpits.impl_traits[idx].bounds.map_ref(|it| it.into_iter())
                     });
                     let var = self.table.new_type_var();
                     let var_subst = Substitution::from1(Interner, var.clone());
@@ -616,6 +620,7 @@ impl<'a> InferenceContext<'a> {
                         always!(binders.is_empty(Interner)); // quantified where clauses not yet handled
                         self.push_obligation(var_predicate.cast(Interner));
                     }
+                    self.result.type_of_rpit.insert(idx, var.clone());
                     var
                 },
                 DebruijnIndex::INNERMOST,
diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs
index cd7f1b805c5..c82c274524a 100644
--- a/crates/hir-ty/src/layout.rs
+++ b/crates/hir-ty/src/layout.rs
@@ -225,10 +225,21 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Lay
             ptr.valid_range_mut().start = 1;
             Layout::scalar(dl, ptr)
         }
-        TyKind::Closure(_, _)
-        | TyKind::OpaqueType(_, _)
-        | TyKind::Generator(_, _)
-        | TyKind::GeneratorWitness(_, _) => return Err(LayoutError::NotImplemented),
+        TyKind::OpaqueType(opaque_ty_id, _) => {
+            let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
+            match impl_trait_id {
+                crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+                    let infer = db.infer(func.into());
+                    layout_of_ty(db, &infer.type_of_rpit[idx], krate)?
+                }
+                crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => {
+                    return Err(LayoutError::NotImplemented)
+                }
+            }
+        }
+        TyKind::Closure(_, _) | TyKind::Generator(_, _) | TyKind::GeneratorWitness(_, _) => {
+            return Err(LayoutError::NotImplemented)
+        }
         TyKind::AssociatedType(_, _)
         | TyKind::Error
         | TyKind::Alias(_)
diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs
index 53838cf41d2..067bdc960da 100644
--- a/crates/hir-ty/src/layout/tests.rs
+++ b/crates/hir-ty/src/layout/tests.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+
 use base_db::fixture::WithFixture;
 use chalk_ir::{AdtId, TyKind};
 use hir_def::{
@@ -5,20 +7,16 @@ use hir_def::{
     layout::{Layout, LayoutError},
 };
 
-use crate::{test_db::TestDB, Interner, Substitution};
+use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution};
 
 use super::layout_of_ty;
 
-fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
-    // using unstable cargo features failed, fall back to using plain rustc
-    let mut cmd = std::process::Command::new("rustc");
-    cmd.args(["-Z", "unstable-options", "--print", "target-spec-json"]).env("RUSTC_BOOTSTRAP", "1");
-    let output = cmd.output().unwrap();
-    assert!(output.status.success(), "{}", output.status);
-    let stdout = String::from_utf8(output.stdout).unwrap();
-    let target_data_layout =
-        stdout.split_once(r#""data-layout": ""#).unwrap().1.split_once('"').unwrap().0.to_owned();
+fn current_machine_data_layout() -> String {
+    project_model::target_data_layout::get(None, None, &HashMap::default()).unwrap()
+}
 
+fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
+    let target_data_layout = current_machine_data_layout();
     let ra_fixture = format!(
         "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\n{ra_fixture}",
     );
@@ -45,6 +43,42 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
     layout_of_ty(&db, &goal_ty, module_id.krate())
 }
 
+/// A version of `eval_goal` for types that can not be expressed in ADTs, like closures and `impl Trait`
+fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
+    let target_data_layout = current_machine_data_layout();
+    let ra_fixture = format!(
+        "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\nfn main(){{let goal = {{{ra_fixture}}};}}",
+    );
+
+    let (db, file_id) = TestDB::with_single_file(&ra_fixture);
+    let module_id = db.module_for_file(file_id);
+    let def_map = module_id.def_map(&db);
+    let scope = &def_map[module_id.local_id].scope;
+    let adt_id = scope
+        .declarations()
+        .find_map(|x| match x {
+            hir_def::ModuleDefId::FunctionId(x) => {
+                let name = db.function_data(x).name.to_smol_str();
+                (name == "main").then_some(x)
+            }
+            _ => None,
+        })
+        .unwrap();
+    let hir_body = db.body(adt_id.into());
+    let pat = hir_body
+        .pats
+        .iter()
+        .find(|x| match x.1 {
+            hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal",
+            _ => false,
+        })
+        .unwrap()
+        .0;
+    let infer = db.infer(adt_id.into());
+    let goal_ty = infer.type_of_pat[pat].clone();
+    layout_of_ty(&db, &goal_ty, module_id.krate())
+}
+
 #[track_caller]
 fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) {
     let l = eval_goal(ra_fixture, minicore).unwrap();
@@ -53,6 +87,13 @@ fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64)
 }
 
 #[track_caller]
+fn check_size_and_align_expr(ra_fixture: &str, minicore: &str, size: u64, align: u64) {
+    let l = eval_expr(ra_fixture, minicore).unwrap();
+    assert_eq!(l.size.bytes(), size);
+    assert_eq!(l.align.abi.bytes(), align);
+}
+
+#[track_caller]
 fn check_fail(ra_fixture: &str, e: LayoutError) {
     let r = eval_goal(ra_fixture, "");
     assert_eq!(r, Err(e));
@@ -85,11 +126,31 @@ macro_rules! size_and_align {
     };
 }
 
+macro_rules! size_and_align_expr {
+    ($($t:tt)*) => {
+        {
+            #[allow(dead_code)]
+            {
+                let val = { $($t)* };
+                check_size_and_align_expr(
+                    stringify!($($t)*),
+                    "",
+                    ::std::mem::size_of_val(&val) as u64,
+                    ::std::mem::align_of_val(&val) as u64,
+                );
+            }
+        }
+    };
+}
+
 #[test]
 fn hello_world() {
     size_and_align! {
         struct Goal(i32);
     }
+    size_and_align_expr! {
+        2i32
+    }
 }
 
 #[test]
@@ -144,6 +205,40 @@ fn generic() {
 }
 
 #[test]
+fn return_position_impl_trait() {
+    size_and_align_expr! {
+        trait T {}
+        impl T for i32 {}
+        impl T for i64 {}
+        fn foo() -> impl T { 2i64 }
+        foo()
+    }
+    size_and_align_expr! {
+        trait T {}
+        impl T for i32 {}
+        impl T for i64 {}
+        fn foo() -> (impl T, impl T, impl T) { (2i64, 5i32, 7i32) }
+        foo()
+    }
+    size_and_align_expr! {
+        struct Foo<T>(T, T, (T, T));
+        trait T {}
+        impl T for Foo<i32> {}
+        impl T for Foo<i64> {}
+
+        fn foo() -> Foo<impl T> { Foo(
+            Foo(1i64, 2, (3, 4)),
+            Foo(5, 6, (7, 8)),
+            (
+                Foo(1i64, 2, (3, 4)),
+                Foo(5, 6, (7, 8)),
+            ),
+        ) }
+        foo()
+    }
+}
+
+#[test]
 fn enums() {
     size_and_align! {
         enum Goal {
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index cbeb61067df..59a5ef8c14d 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -45,6 +45,7 @@ use chalk_ir::{
 use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
 use hir_expand::name;
 use itertools::Either;
+use la_arena::{Arena, Idx};
 use rustc_hash::FxHashSet;
 use traits::FnTrait;
 use utils::Generics;
@@ -290,22 +291,24 @@ impl TypeFoldable<Interner> for CallableSig {
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
 pub enum ImplTraitId {
-    ReturnTypeImplTrait(hir_def::FunctionId, u16),
+    ReturnTypeImplTrait(hir_def::FunctionId, RpitId),
     AsyncBlockTypeImplTrait(hir_def::DefWithBodyId, ExprId),
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub struct ReturnTypeImplTraits {
-    pub(crate) impl_traits: Vec<ReturnTypeImplTrait>,
+    pub(crate) impl_traits: Arena<ReturnTypeImplTrait>,
 }
 
 has_interner!(ReturnTypeImplTraits);
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
-pub(crate) struct ReturnTypeImplTrait {
+pub struct ReturnTypeImplTrait {
     pub(crate) bounds: Binders<Vec<QuantifiedWhereClause>>,
 }
 
+pub type RpitId = Idx<ReturnTypeImplTrait>;
+
 pub fn static_lifetime() -> Lifetime {
     LifetimeData::Static.intern(Interner)
 }
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index b1a7ad3e940..86abe1af68a 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -36,7 +36,7 @@ use hir_def::{
 use hir_expand::{name::Name, ExpandResult};
 use intern::Interned;
 use itertools::Either;
-use la_arena::ArenaMap;
+use la_arena::{Arena, ArenaMap};
 use rustc_hash::FxHashSet;
 use smallvec::SmallVec;
 use stdx::{impl_from, never};
@@ -58,6 +58,51 @@ use crate::{
 };
 
 #[derive(Debug)]
+enum ImplTraitLoweringState {
+    /// When turning `impl Trait` into opaque types, we have to collect the
+    /// bounds at the same time to get the IDs correct (without becoming too
+    /// complicated). I don't like using interior mutability (as for the
+    /// counter), but I've tried and failed to make the lifetimes work for
+    /// passing around a `&mut TyLoweringContext`. The core problem is that
+    /// we're grouping the mutable data (the counter and this field) together
+    /// with the immutable context (the references to the DB and resolver).
+    /// Splitting this up would be a possible fix.
+    Opaque(RefCell<Arena<ReturnTypeImplTrait>>),
+    Param(Cell<u16>),
+    Variable(Cell<u16>),
+    Disallowed,
+}
+impl ImplTraitLoweringState {
+    fn new(impl_trait_mode: ImplTraitLoweringMode) -> ImplTraitLoweringState {
+        match impl_trait_mode {
+            ImplTraitLoweringMode::Opaque => Self::Opaque(RefCell::new(Arena::new())),
+            ImplTraitLoweringMode::Param => Self::Param(Cell::new(0)),
+            ImplTraitLoweringMode::Variable => Self::Variable(Cell::new(0)),
+            ImplTraitLoweringMode::Disallowed => Self::Disallowed,
+        }
+    }
+
+    fn take(&self) -> Self {
+        match self {
+            Self::Opaque(x) => Self::Opaque(RefCell::new(x.take())),
+            Self::Param(x) => Self::Param(Cell::new(x.get())),
+            Self::Variable(x) => Self::Variable(Cell::new(x.get())),
+            Self::Disallowed => Self::Disallowed,
+        }
+    }
+
+    fn swap(&self, impl_trait_mode: &Self) {
+        match (self, impl_trait_mode) {
+            (Self::Opaque(x), Self::Opaque(y)) => x.swap(y),
+            (Self::Param(x), Self::Param(y)) => x.swap(y),
+            (Self::Variable(x), Self::Variable(y)) => x.swap(y),
+            (Self::Disallowed, Self::Disallowed) => (),
+            _ => panic!("mismatched lowering mode"),
+        }
+    }
+}
+
+#[derive(Debug)]
 pub struct TyLoweringContext<'a> {
     pub db: &'a dyn HirDatabase,
     pub resolver: &'a Resolver,
@@ -67,17 +112,7 @@ pub struct TyLoweringContext<'a> {
     /// should be converted to variables. I think in practice, this isn't
     /// possible currently, so this should be fine for now.
     pub type_param_mode: ParamLoweringMode,
-    pub impl_trait_mode: ImplTraitLoweringMode,
-    impl_trait_counter: Cell<u16>,
-    /// When turning `impl Trait` into opaque types, we have to collect the
-    /// bounds at the same time to get the IDs correct (without becoming too
-    /// complicated). I don't like using interior mutability (as for the
-    /// counter), but I've tried and failed to make the lifetimes work for
-    /// passing around a `&mut TyLoweringContext`. The core problem is that
-    /// we're grouping the mutable data (the counter and this field) together
-    /// with the immutable context (the references to the DB and resolver).
-    /// Splitting this up would be a possible fix.
-    opaque_type_data: RefCell<Vec<ReturnTypeImplTrait>>,
+    impl_trait_mode: ImplTraitLoweringState,
     expander: RefCell<Option<Expander>>,
     /// Tracks types with explicit `?Sized` bounds.
     pub(crate) unsized_types: RefCell<FxHashSet<Ty>>,
@@ -85,19 +120,15 @@ pub struct TyLoweringContext<'a> {
 
 impl<'a> TyLoweringContext<'a> {
     pub fn new(db: &'a dyn HirDatabase, resolver: &'a Resolver) -> Self {
-        let impl_trait_counter = Cell::new(0);
-        let impl_trait_mode = ImplTraitLoweringMode::Disallowed;
+        let impl_trait_mode = ImplTraitLoweringState::Disallowed;
         let type_param_mode = ParamLoweringMode::Placeholder;
         let in_binders = DebruijnIndex::INNERMOST;
-        let opaque_type_data = RefCell::new(Vec::new());
         Self {
             db,
             resolver,
             in_binders,
             impl_trait_mode,
-            impl_trait_counter,
             type_param_mode,
-            opaque_type_data,
             expander: RefCell::new(None),
             unsized_types: RefCell::default(),
         }
@@ -108,20 +139,18 @@ impl<'a> TyLoweringContext<'a> {
         debruijn: DebruijnIndex,
         f: impl FnOnce(&TyLoweringContext<'_>) -> T,
     ) -> T {
-        let opaque_ty_data_vec = self.opaque_type_data.take();
+        let impl_trait_mode = self.impl_trait_mode.take();
         let expander = self.expander.take();
         let unsized_types = self.unsized_types.take();
         let new_ctx = Self {
             in_binders: debruijn,
-            impl_trait_counter: Cell::new(self.impl_trait_counter.get()),
-            opaque_type_data: RefCell::new(opaque_ty_data_vec),
+            impl_trait_mode,
             expander: RefCell::new(expander),
             unsized_types: RefCell::new(unsized_types),
             ..*self
         };
         let result = f(&new_ctx);
-        self.impl_trait_counter.set(new_ctx.impl_trait_counter.get());
-        self.opaque_type_data.replace(new_ctx.opaque_type_data.into_inner());
+        self.impl_trait_mode.swap(&new_ctx.impl_trait_mode);
         self.expander.replace(new_ctx.expander.into_inner());
         self.unsized_types.replace(new_ctx.unsized_types.into_inner());
         result
@@ -136,7 +165,7 @@ impl<'a> TyLoweringContext<'a> {
     }
 
     pub fn with_impl_trait_mode(self, impl_trait_mode: ImplTraitLoweringMode) -> Self {
-        Self { impl_trait_mode, ..self }
+        Self { impl_trait_mode: ImplTraitLoweringState::new(impl_trait_mode), ..self }
     }
 
     pub fn with_type_param_mode(self, type_param_mode: ParamLoweringMode) -> Self {
@@ -244,20 +273,17 @@ impl<'a> TyLoweringContext<'a> {
             }
             TypeRef::DynTrait(bounds) => self.lower_dyn_trait(bounds),
             TypeRef::ImplTrait(bounds) => {
-                match self.impl_trait_mode {
-                    ImplTraitLoweringMode::Opaque => {
-                        let idx = self.impl_trait_counter.get();
-                        self.impl_trait_counter.set(idx + 1);
+                match &self.impl_trait_mode {
+                    ImplTraitLoweringState::Opaque(opaque_type_data) => {
                         let func = match self.resolver.generic_def() {
                             Some(GenericDefId::FunctionId(f)) => f,
                             _ => panic!("opaque impl trait lowering in non-function"),
                         };
 
-                        assert!(idx as usize == self.opaque_type_data.borrow().len());
                         // this dance is to make sure the data is in the right
                         // place even if we encounter more opaque types while
                         // lowering the bounds
-                        self.opaque_type_data.borrow_mut().push(ReturnTypeImplTrait {
+                        let idx = opaque_type_data.borrow_mut().alloc(ReturnTypeImplTrait {
                             bounds: crate::make_single_type_binders(Vec::new()),
                         });
                         // We don't want to lower the bounds inside the binders
@@ -273,7 +299,7 @@ impl<'a> TyLoweringContext<'a> {
                             .with_debruijn(DebruijnIndex::INNERMOST, |ctx| {
                                 ctx.lower_impl_trait(bounds, func)
                             });
-                        self.opaque_type_data.borrow_mut()[idx as usize] = actual_opaque_type_data;
+                        opaque_type_data.borrow_mut()[idx] = actual_opaque_type_data;
 
                         let impl_trait_id = ImplTraitId::ReturnTypeImplTrait(func, idx);
                         let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into();
@@ -281,10 +307,10 @@ impl<'a> TyLoweringContext<'a> {
                         let parameters = generics.bound_vars_subst(self.db, self.in_binders);
                         TyKind::OpaqueType(opaque_ty_id, parameters).intern(Interner)
                     }
-                    ImplTraitLoweringMode::Param => {
-                        let idx = self.impl_trait_counter.get();
+                    ImplTraitLoweringState::Param(counter) => {
+                        let idx = counter.get();
                         // FIXME we're probably doing something wrong here
-                        self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
+                        counter.set(idx + count_impl_traits(type_ref) as u16);
                         if let Some(def) = self.resolver.generic_def() {
                             let generics = generics(self.db.upcast(), def);
                             let param = generics
@@ -305,10 +331,10 @@ impl<'a> TyLoweringContext<'a> {
                             TyKind::Error.intern(Interner)
                         }
                     }
-                    ImplTraitLoweringMode::Variable => {
-                        let idx = self.impl_trait_counter.get();
+                    ImplTraitLoweringState::Variable(counter) => {
+                        let idx = counter.get();
                         // FIXME we're probably doing something wrong here
-                        self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
+                        counter.set(idx + count_impl_traits(type_ref) as u16);
                         let (
                             _parent_params,
                             self_params,
@@ -327,7 +353,7 @@ impl<'a> TyLoweringContext<'a> {
                         ))
                         .intern(Interner)
                     }
-                    ImplTraitLoweringMode::Disallowed => {
+                    ImplTraitLoweringState::Disallowed => {
                         // FIXME: report error
                         TyKind::Error.intern(Interner)
                     }
@@ -1863,8 +1889,12 @@ pub(crate) fn return_type_impl_traits(
         .with_type_param_mode(ParamLoweringMode::Variable);
     let _ret = ctx_ret.lower_ty(&data.ret_type);
     let generics = generics(db.upcast(), def.into());
-    let return_type_impl_traits =
-        ReturnTypeImplTraits { impl_traits: ctx_ret.opaque_type_data.into_inner() };
+    let return_type_impl_traits = ReturnTypeImplTraits {
+        impl_traits: match ctx_ret.impl_trait_mode {
+            ImplTraitLoweringState::Opaque(x) => x.into_inner(),
+            _ => unreachable!(),
+        },
+    };
     if return_type_impl_traits.impl_traits.is_empty() {
         None
     } else {
diff --git a/crates/ide-assists/src/handlers/add_braces.rs b/crates/ide-assists/src/handlers/add_braces.rs
new file mode 100644
index 00000000000..2f4a263ee07
--- /dev/null
+++ b/crates/ide-assists/src/handlers/add_braces.rs
@@ -0,0 +1,155 @@
+use syntax::{
+    ast::{self, edit::AstNodeEdit, make},
+    AstNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_braces
+//
+// Adds braces to lambda and match arm expressions.
+//
+// ```
+// fn foo(n: i32) -> i32 {
+//     match n {
+//         1 =>$0 n + 1,
+//         _ => 0
+//     }
+// }
+// ```
+// ->
+// ```
+// fn foo(n: i32) -> i32 {
+//     match n {
+//         1 => {
+//             n + 1
+//         },
+//         _ => 0
+//     }
+// }
+// ```
+pub(crate) fn add_braces(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let (expr_type, expr) = get_replacement_node(ctx)?;
+
+    acc.add(
+        AssistId("add_braces", AssistKind::RefactorRewrite),
+        match expr_type {
+            ParentType::ClosureExpr => "Add braces to closure body",
+            ParentType::MatchArmExpr => "Add braces to arm expression",
+        },
+        expr.syntax().text_range(),
+        |builder| {
+            let block_expr = AstNodeEdit::indent(
+                &make::block_expr(None, Some(expr.clone())),
+                AstNodeEdit::indent_level(&expr),
+            );
+
+            builder.replace(expr.syntax().text_range(), block_expr.syntax().text());
+        },
+    )
+}
+
+enum ParentType {
+    MatchArmExpr,
+    ClosureExpr,
+}
+
+fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Expr)> {
+    if let Some(match_arm) = ctx.find_node_at_offset::<ast::MatchArm>() {
+        let match_arm_expr = match_arm.expr()?;
+
+        if matches!(match_arm_expr, ast::Expr::BlockExpr(_)) {
+            return None;
+        }
+
+        return Some((ParentType::MatchArmExpr, match_arm_expr));
+    } else if let Some(closure_expr) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
+        let body = closure_expr.body()?;
+
+        if matches!(body, ast::Expr::BlockExpr(_)) {
+            return None;
+        }
+
+        return Some((ParentType::ClosureExpr, body));
+    }
+
+    None
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn suggest_add_braces_for_closure() {
+        check_assist(
+            add_braces,
+            r#"
+fn foo() {
+    t(|n|$0 n + 100);
+}
+"#,
+            r#"
+fn foo() {
+    t(|n| {
+        n + 100
+    });
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn no_assist_for_closures_with_braces() {
+        check_assist_not_applicable(
+            add_braces,
+            r#"
+fn foo() {
+    t(|n|$0 { n + 100 });
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn suggest_add_braces_for_match() {
+        check_assist(
+            add_braces,
+            r#"
+fn foo() {
+    match n {
+        Some(n) $0=> 29,
+        _ => ()
+    };
+}
+"#,
+            r#"
+fn foo() {
+    match n {
+        Some(n) => {
+            29
+        },
+        _ => ()
+    };
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn no_assist_for_match_with_braces() {
+        check_assist_not_applicable(
+            add_braces,
+            r#"
+fn foo() {
+    match n {
+        Some(n) $0=> { return 29; },
+        _ => ()
+    };
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 546ef96260f..276cf5f5dd0 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -106,6 +106,7 @@ mod handlers {
 
     pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>;
 
+    mod add_braces;
     mod add_explicit_type;
     mod add_label_to_loop;
     mod add_lifetime_to_type;
@@ -209,6 +210,7 @@ mod handlers {
     pub(crate) fn all() -> &'static [Handler] {
         &[
             // These are alphabetic for the foolish consistency
+            add_braces::add_braces,
             add_explicit_type::add_explicit_type,
             add_label_to_loop::add_label_to_loop,
             add_missing_match_arms::add_missing_match_arms,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 16a06b60de9..8a25e1f648a 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -3,6 +3,31 @@
 use super::check_doc_test;
 
 #[test]
+fn doctest_add_braces() {
+    check_doc_test(
+        "add_braces",
+        r#####"
+fn foo(n: i32) -> i32 {
+    match n {
+        1 =>$0 n + 1,
+        _ => 0
+    }
+}
+"#####,
+        r#####"
+fn foo(n: i32) -> i32 {
+    match n {
+        1 => {
+            n + 1
+        },
+        _ => 0
+    }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_add_explicit_type() {
     check_doc_test(
         "add_explicit_type",
diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs
index 45e85a338a4..188eb7f977b 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -6,6 +6,7 @@
 use hir::{Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, PointerCast, Safety, Semantics};
 use ide_db::RootDatabase;
 
+use stdx::never;
 use syntax::{
     ast::{self, make, AstNode},
     ted,
@@ -210,16 +211,21 @@ fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool,
     ted::replace(expr.syntax(), dummy_expr.syntax());
 
     let parent = dummy_expr.syntax().parent();
-    let expr = if postfix {
-        let ast::Expr::TryExpr(e) = &dummy_expr else { unreachable!() };
-        let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+    let Some(expr) = (|| {
+        if postfix {
+            let ast::Expr::TryExpr(e) = &dummy_expr else { return None };
+            let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
 
-        e.expr().unwrap()
-    } else {
-        let ast::Expr::RefExpr(e) = &dummy_expr else { unreachable!() };
-        let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+            e.expr()
+        } else {
+            let ast::Expr::RefExpr(e) = &dummy_expr else { return None };
+            let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
 
-        e.expr().unwrap()
+            e.expr()
+        }
+    })() else {
+        never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr);
+        return (true, true)
     };
 
     // At this point
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index fbf6b53006a..fb531340108 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -95,6 +95,9 @@ pub fn token_tree_to_syntax_node(
             parser::Step::Token { kind, n_input_tokens: n_raw_tokens } => {
                 tree_sink.token(kind, n_raw_tokens)
             }
+            parser::Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+                tree_sink.float_split(has_pseudo_dot)
+            }
             parser::Step::Enter { kind } => tree_sink.start_node(kind),
             parser::Step::Exit => tree_sink.finish_node(),
             parser::Step::Error { msg } => tree_sink.error(msg.to_string()),
@@ -796,6 +799,43 @@ fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> {
 }
 
 impl<'a> TtTreeSink<'a> {
+    /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween.
+    /// This occurs when a float literal is used as a field access.
+    fn float_split(&mut self, has_pseudo_dot: bool) {
+        let (text, _span) = match self.cursor.token_tree() {
+            Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Literal(lit), _)) => {
+                (lit.text.as_str(), lit.span)
+            }
+            _ => unreachable!(),
+        };
+        match text.split_once('.') {
+            Some((left, right)) => {
+                assert!(!left.is_empty());
+                self.inner.start_node(SyntaxKind::NAME_REF);
+                self.inner.token(SyntaxKind::INT_NUMBER, left);
+                self.inner.finish_node();
+
+                // here we move the exit up, the original exit has been deleted in process
+                self.inner.finish_node();
+
+                self.inner.token(SyntaxKind::DOT, ".");
+
+                if has_pseudo_dot {
+                    assert!(right.is_empty(), "{left}.{right}");
+                } else {
+                    self.inner.start_node(SyntaxKind::NAME_REF);
+                    self.inner.token(SyntaxKind::INT_NUMBER, right);
+                    self.inner.finish_node();
+
+                    // the parser creates an unbalanced start node, we are required to close it here
+                    self.inner.finish_node();
+                }
+            }
+            None => unreachable!(),
+        }
+        self.cursor = self.cursor.bump();
+    }
+
     fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
         if kind == LIFETIME_IDENT {
             n_tokens = 2;
diff --git a/crates/mbe/src/to_parser_input.rs b/crates/mbe/src/to_parser_input.rs
index d4c19b3ab8c..051e20b3a3f 100644
--- a/crates/mbe/src/to_parser_input.rs
+++ b/crates/mbe/src/to_parser_input.rs
@@ -45,6 +45,13 @@ pub(crate) fn to_parser_input(buffer: &TokenBuffer<'_>) -> parser::Input {
                             .unwrap_or_else(|| panic!("Fail to convert given literal {:#?}", &lit));
 
                         res.push(kind);
+
+                        if kind == FLOAT_NUMBER && !inner_text.ends_with('.') {
+                            // Tag the token as joint if it is float with a fractional part
+                            // we use this jointness to inform the parser about what token split
+                            // event to emit when we encounter a float literal in a field access
+                            res.was_joint();
+                        }
                     }
                     tt::Leaf::Ident(ident) => match ident.text.as_ref() {
                         "_" => res.push(T![_]),
diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs
index e5f6b137220..f744481f3ae 100644
--- a/crates/mbe/src/tt_iter.rs
+++ b/crates/mbe/src/tt_iter.rs
@@ -150,6 +150,11 @@ impl<'a> TtIter<'a> {
                         cursor = cursor.bump_subtree();
                     }
                 }
+                parser::Step::FloatSplit { .. } => {
+                    // FIXME: We need to split the tree properly here, but mutating the token trees
+                    // in the buffer is somewhat tricky to pull off.
+                    cursor = cursor.bump_subtree();
+                }
                 parser::Step::Enter { .. } | parser::Step::Exit => (),
                 parser::Step::Error { .. } => error = true,
             }
@@ -166,19 +171,18 @@ impl<'a> TtIter<'a> {
 
         if cursor.is_root() {
             while curr != cursor {
-                if let Some(token) = curr.token_tree() {
-                    res.push(token);
-                }
+                let Some(token) = curr.token_tree() else { break };
+                res.push(token.cloned());
                 curr = curr.bump();
             }
         }
+
         self.inner = self.inner.as_slice()[res.len()..].iter();
         let res = match res.len() {
-            1 => Some(res[0].cloned()),
-            0 => None,
+            0 | 1 => res.pop(),
             _ => Some(tt::TokenTree::Subtree(tt::Subtree {
                 delimiter: tt::Delimiter::unspecified(),
-                token_trees: res.into_iter().map(|it| it.cloned()).collect(),
+                token_trees: res,
             })),
         };
         ExpandResult { value: res, err }
diff --git a/crates/parser/src/event.rs b/crates/parser/src/event.rs
index b0e70e79430..577eb0967b4 100644
--- a/crates/parser/src/event.rs
+++ b/crates/parser/src/event.rs
@@ -74,7 +74,13 @@ pub(crate) enum Event {
         kind: SyntaxKind,
         n_raw_tokens: u8,
     },
-
+    /// When we parse `foo.0.0` or `foo. 0. 0` the lexer will hand us a float literal
+    /// instead of an integer literal followed by a dot as the lexer has no contextual knowledge.
+    /// This event instructs whatever consumes the events to split the float literal into
+    /// the corresponding parts.
+    FloatSplitHack {
+        ends_in_dot: bool,
+    },
     Error {
         msg: String,
     },
@@ -125,6 +131,11 @@ pub(super) fn process(mut events: Vec<Event>) -> Output {
             Event::Token { kind, n_raw_tokens } => {
                 res.token(kind, n_raw_tokens);
             }
+            Event::FloatSplitHack { ends_in_dot } => {
+                res.float_split_hack(ends_in_dot);
+                let ev = mem::replace(&mut events[i + 1], Event::tombstone());
+                assert!(matches!(ev, Event::Finish), "{ev:?}");
+            }
             Event::Error { msg } => res.error(msg),
         }
     }
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 8932330b825..7516ac3c4bd 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -379,7 +379,7 @@ fn postfix_expr(
             // }
             T!['('] if allow_calls => call_expr(p, lhs),
             T!['['] if allow_calls => index_expr(p, lhs),
-            T![.] => match postfix_dot_expr(p, lhs) {
+            T![.] => match postfix_dot_expr::<false>(p, lhs) {
                 Ok(it) => it,
                 Err(it) => {
                     lhs = it;
@@ -393,35 +393,44 @@ fn postfix_expr(
         block_like = BlockLike::NotBlock;
     }
     return (lhs, block_like);
+}
 
-    fn postfix_dot_expr(
-        p: &mut Parser<'_>,
-        lhs: CompletedMarker,
-    ) -> Result<CompletedMarker, CompletedMarker> {
+fn postfix_dot_expr<const FLOAT_RECOVERY: bool>(
+    p: &mut Parser<'_>,
+    lhs: CompletedMarker,
+) -> Result<CompletedMarker, CompletedMarker> {
+    if !FLOAT_RECOVERY {
         assert!(p.at(T![.]));
-        if p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) {
-            return Ok(method_call_expr(p, lhs));
-        }
+    }
+    let nth1 = if FLOAT_RECOVERY { 0 } else { 1 };
+    let nth2 = if FLOAT_RECOVERY { 1 } else { 2 };
 
-        // test await_expr
-        // fn foo() {
-        //     x.await;
-        //     x.0.await;
-        //     x.0().await?.hello();
-        // }
-        if p.nth(1) == T![await] {
-            let m = lhs.precede(p);
-            p.bump(T![.]);
-            p.bump(T![await]);
-            return Ok(m.complete(p, AWAIT_EXPR));
-        }
+    if p.nth(nth1) == IDENT && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) {
+        return Ok(method_call_expr::<FLOAT_RECOVERY>(p, lhs));
+    }
 
-        if p.at(T![..=]) || p.at(T![..]) {
-            return Err(lhs);
+    // test await_expr
+    // fn foo() {
+    //     x.await;
+    //     x.0.await;
+    //     x.0().await?.hello();
+    //     x.0.0.await;
+    //     x.0. await;
+    // }
+    if p.nth(nth1) == T![await] {
+        let m = lhs.precede(p);
+        if !FLOAT_RECOVERY {
+            p.bump(T![.]);
         }
+        p.bump(T![await]);
+        return Ok(m.complete(p, AWAIT_EXPR));
+    }
 
-        Ok(field_expr(p, lhs))
+    if p.at(T![..=]) || p.at(T![..]) {
+        return Err(lhs);
     }
+
+    field_expr::<FLOAT_RECOVERY>(p, lhs)
 }
 
 // test call_expr
@@ -455,11 +464,22 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
 // fn foo() {
 //     x.foo();
 //     y.bar::<T>(1, 2,);
+//     x.0.0.call();
+//     x.0. call();
 // }
-fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
-    assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])));
+fn method_call_expr<const FLOAT_RECOVERY: bool>(
+    p: &mut Parser<'_>,
+    lhs: CompletedMarker,
+) -> CompletedMarker {
+    if FLOAT_RECOVERY {
+        assert!(p.nth(0) == IDENT && (p.nth(1) == T!['('] || p.nth_at(1, T![::])));
+    } else {
+        assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])));
+    }
     let m = lhs.precede(p);
-    p.bump_any();
+    if !FLOAT_RECOVERY {
+        p.bump(T![.]);
+    }
     name_ref(p);
     generic_args::opt_generic_arg_list(p, true);
     if p.at(T!['(']) {
@@ -472,21 +492,35 @@ fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker
 // fn foo() {
 //     x.foo;
 //     x.0.bar;
+//     x.0.1;
+//     x.0. bar;
 //     x.0();
 // }
-fn field_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
-    assert!(p.at(T![.]));
+fn field_expr<const FLOAT_RECOVERY: bool>(
+    p: &mut Parser<'_>,
+    lhs: CompletedMarker,
+) -> Result<CompletedMarker, CompletedMarker> {
+    if !FLOAT_RECOVERY {
+        assert!(p.at(T![.]));
+    }
     let m = lhs.precede(p);
-    p.bump(T![.]);
+    if !FLOAT_RECOVERY {
+        p.bump(T![.]);
+    }
     if p.at(IDENT) || p.at(INT_NUMBER) {
         name_ref_or_index(p);
     } else if p.at(FLOAT_NUMBER) {
-        // FIXME: How to recover and instead parse INT + T![.]?
-        p.bump_any();
+        return match p.split_float(m) {
+            (true, m) => {
+                let lhs = m.complete(p, FIELD_EXPR);
+                postfix_dot_expr::<true>(p, lhs)
+            }
+            (false, m) => Ok(m.complete(p, FIELD_EXPR)),
+        };
     } else {
         p.error("expected field name or number");
     }
-    m.complete(p, FIELD_EXPR)
+    Ok(m.complete(p, FIELD_EXPR))
 }
 
 // test try_expr
diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs
index 87be4792773..8c5aed0232b 100644
--- a/crates/parser/src/lib.rs
+++ b/crates/parser/src/lib.rs
@@ -102,10 +102,14 @@ impl TopEntryPoint {
                 match step {
                     Step::Enter { .. } => depth += 1,
                     Step::Exit => depth -= 1,
+                    Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+                        depth -= 1 + !has_pseudo_dot as usize
+                    }
                     Step::Token { .. } | Step::Error { .. } => (),
                 }
             }
             assert!(!first, "no tree at all");
+            assert_eq!(depth, 0, "unbalanced tree");
         }
 
         res
diff --git a/crates/parser/src/output.rs b/crates/parser/src/output.rs
index 6ca841cfe07..41d4c68b2d7 100644
--- a/crates/parser/src/output.rs
+++ b/crates/parser/src/output.rs
@@ -25,53 +25,88 @@ pub struct Output {
 #[derive(Debug)]
 pub enum Step<'a> {
     Token { kind: SyntaxKind, n_input_tokens: u8 },
+    FloatSplit { ends_in_dot: bool },
     Enter { kind: SyntaxKind },
     Exit,
     Error { msg: &'a str },
 }
 
 impl Output {
+    const EVENT_MASK: u32 = 0b1;
+    const TAG_MASK: u32 = 0x0000_00F0;
+    const N_INPUT_TOKEN_MASK: u32 = 0x0000_FF00;
+    const KIND_MASK: u32 = 0xFFFF_0000;
+
+    const ERROR_SHIFT: u32 = Self::EVENT_MASK.trailing_ones();
+    const TAG_SHIFT: u32 = Self::TAG_MASK.trailing_zeros();
+    const N_INPUT_TOKEN_SHIFT: u32 = Self::N_INPUT_TOKEN_MASK.trailing_zeros();
+    const KIND_SHIFT: u32 = Self::KIND_MASK.trailing_zeros();
+
+    const TOKEN_EVENT: u8 = 0;
+    const ENTER_EVENT: u8 = 1;
+    const EXIT_EVENT: u8 = 2;
+    const SPLIT_EVENT: u8 = 3;
+
     pub fn iter(&self) -> impl Iterator<Item = Step<'_>> {
         self.event.iter().map(|&event| {
-            if event & 0b1 == 0 {
-                return Step::Error { msg: self.error[(event as usize) >> 1].as_str() };
+            if event & Self::EVENT_MASK == 0 {
+                return Step::Error {
+                    msg: self.error[(event as usize) >> Self::ERROR_SHIFT].as_str(),
+                };
             }
-            let tag = ((event & 0x0000_00F0) >> 4) as u8;
+            let tag = ((event & Self::TAG_MASK) >> Self::TAG_SHIFT) as u8;
             match tag {
-                0 => {
-                    let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into();
-                    let n_input_tokens = ((event & 0x0000_FF00) >> 8) as u8;
+                Self::TOKEN_EVENT => {
+                    let kind: SyntaxKind =
+                        (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into();
+                    let n_input_tokens =
+                        ((event & Self::N_INPUT_TOKEN_MASK) >> Self::N_INPUT_TOKEN_SHIFT) as u8;
                     Step::Token { kind, n_input_tokens }
                 }
-                1 => {
-                    let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into();
+                Self::ENTER_EVENT => {
+                    let kind: SyntaxKind =
+                        (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into();
                     Step::Enter { kind }
                 }
-                2 => Step::Exit,
+                Self::EXIT_EVENT => Step::Exit,
+                Self::SPLIT_EVENT => {
+                    Step::FloatSplit { ends_in_dot: event & Self::N_INPUT_TOKEN_MASK != 0 }
+                }
                 _ => unreachable!(),
             }
         })
     }
 
     pub(crate) fn token(&mut self, kind: SyntaxKind, n_tokens: u8) {
-        let e = ((kind as u16 as u32) << 16) | ((n_tokens as u32) << 8) | 1;
+        let e = ((kind as u16 as u32) << Self::KIND_SHIFT)
+            | ((n_tokens as u32) << Self::N_INPUT_TOKEN_SHIFT)
+            | Self::EVENT_MASK;
         self.event.push(e)
     }
 
+    pub(crate) fn float_split_hack(&mut self, ends_in_dot: bool) {
+        let e = (Self::SPLIT_EVENT as u32) << Self::TAG_SHIFT
+            | ((ends_in_dot as u32) << Self::N_INPUT_TOKEN_SHIFT)
+            | Self::EVENT_MASK;
+        self.event.push(e);
+    }
+
     pub(crate) fn enter_node(&mut self, kind: SyntaxKind) {
-        let e = ((kind as u16 as u32) << 16) | (1 << 4) | 1;
+        let e = ((kind as u16 as u32) << Self::KIND_SHIFT)
+            | ((Self::ENTER_EVENT as u32) << Self::TAG_SHIFT)
+            | Self::EVENT_MASK;
         self.event.push(e)
     }
 
     pub(crate) fn leave_node(&mut self) {
-        let e = 2 << 4 | 1;
+        let e = (Self::EXIT_EVENT as u32) << Self::TAG_SHIFT | Self::EVENT_MASK;
         self.event.push(e)
     }
 
     pub(crate) fn error(&mut self, error: String) {
         let idx = self.error.len();
         self.error.push(error);
-        let e = (idx as u32) << 1;
+        let e = (idx as u32) << Self::ERROR_SHIFT;
         self.event.push(e);
     }
 }
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index 48aecb35be1..280416ae7c9 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -181,6 +181,35 @@ impl<'t> Parser<'t> {
         self.do_bump(kind, 1);
     }
 
+    /// Advances the parser by one token
+    pub(crate) fn split_float(&mut self, mut marker: Marker) -> (bool, Marker) {
+        assert!(self.at(SyntaxKind::FLOAT_NUMBER));
+        // we have parse `<something>.`
+        // `<something>`.0.1
+        // here we need to insert an extra event
+        //
+        // `<something>`. 0. 1;
+        // here we need to change the follow up parse, the return value will cause us to emulate a dot
+        // the actual splitting happens later
+        let ends_in_dot = !self.inp.is_joint(self.pos);
+        if !ends_in_dot {
+            let new_marker = self.start();
+            let idx = marker.pos as usize;
+            match &mut self.events[idx] {
+                Event::Start { forward_parent, kind } => {
+                    *kind = SyntaxKind::FIELD_EXPR;
+                    *forward_parent = Some(new_marker.pos - marker.pos);
+                }
+                _ => unreachable!(),
+            }
+            marker.bomb.defuse();
+            marker = new_marker;
+        };
+        self.pos += 1 as usize;
+        self.push_event(Event::FloatSplitHack { ends_in_dot });
+        (ends_in_dot, marker)
+    }
+
     /// Advances the parser by one token, remapping its kind.
     /// This is useful to create contextual keywords from
     /// identifiers. For example, the lexer creates a `union`
diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs
index 2be4050d135..47e4adcbbe6 100644
--- a/crates/parser/src/shortcuts.rs
+++ b/crates/parser/src/shortcuts.rs
@@ -43,7 +43,16 @@ impl<'a> LexedStr<'a> {
                         res.was_joint();
                     }
                     res.push(kind);
+                    // Tag the token as joint if it is float with a fractional part
+                    // we use this jointness to inform the parser about what token split
+                    // event to emit when we encounter a float literal in a field access
+                    if kind == SyntaxKind::FLOAT_NUMBER {
+                        if !self.text(i).ends_with('.') {
+                            res.was_joint();
+                        }
+                    }
                 }
+
                 was_joint = true;
             }
         }
@@ -63,6 +72,9 @@ impl<'a> LexedStr<'a> {
                 Step::Token { kind, n_input_tokens: n_raw_tokens } => {
                     builder.token(kind, n_raw_tokens)
                 }
+                Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+                    builder.float_split(has_pseudo_dot)
+                }
                 Step::Enter { kind } => builder.enter(kind),
                 Step::Exit => builder.exit(),
                 Step::Error { msg } => {
@@ -109,6 +121,16 @@ impl Builder<'_, '_> {
         self.do_token(kind, n_tokens as usize);
     }
 
+    fn float_split(&mut self, has_pseudo_dot: bool) {
+        match mem::replace(&mut self.state, State::Normal) {
+            State::PendingEnter => unreachable!(),
+            State::PendingExit => (self.sink)(StrStep::Exit),
+            State::Normal => (),
+        }
+        self.eat_trivias();
+        self.do_float_split(has_pseudo_dot);
+    }
+
     fn enter(&mut self, kind: SyntaxKind) {
         match mem::replace(&mut self.state, State::Normal) {
             State::PendingEnter => {
@@ -164,6 +186,37 @@ impl Builder<'_, '_> {
         self.pos += n_tokens;
         (self.sink)(StrStep::Token { kind, text });
     }
+
+    fn do_float_split(&mut self, has_pseudo_dot: bool) {
+        let text = &self.lexed.range_text(self.pos..self.pos + 1);
+        self.pos += 1;
+        match text.split_once('.') {
+            Some((left, right)) => {
+                assert!(!left.is_empty());
+                (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF });
+                (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: left });
+                (self.sink)(StrStep::Exit);
+
+                // here we move the exit up, the original exit has been deleted in process
+                (self.sink)(StrStep::Exit);
+
+                (self.sink)(StrStep::Token { kind: SyntaxKind::DOT, text: "." });
+
+                if has_pseudo_dot {
+                    assert!(right.is_empty(), "{left}.{right}");
+                    self.state = State::Normal;
+                } else {
+                    (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF });
+                    (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: right });
+                    (self.sink)(StrStep::Exit);
+
+                    // the parser creates an unbalanced start node, we are required to close it here
+                    self.state = State::PendingExit;
+                }
+            }
+            None => unreachable!(),
+        }
+    }
 }
 
 fn n_attached_trivias<'a>(
diff --git a/crates/parser/src/tests/prefix_entries.rs b/crates/parser/src/tests/prefix_entries.rs
index e626b4f27e0..40f92e58804 100644
--- a/crates/parser/src/tests/prefix_entries.rs
+++ b/crates/parser/src/tests/prefix_entries.rs
@@ -51,6 +51,9 @@ fn expr() {
     check(PrefixEntryPoint::Expr, "-1", "-1");
     check(PrefixEntryPoint::Expr, "fn foo() {}", "fn");
     check(PrefixEntryPoint::Expr, "#[attr] ()", "#[attr] ()");
+    check(PrefixEntryPoint::Expr, "foo.0", "foo.0");
+    check(PrefixEntryPoint::Expr, "foo.0.1", "foo.0.1");
+    check(PrefixEntryPoint::Expr, "foo.0. foo", "foo.0. foo");
 }
 
 #[test]
@@ -88,6 +91,7 @@ fn check(entry: PrefixEntryPoint, input: &str, prefix: &str) {
     for step in entry.parse(&input).iter() {
         match step {
             Step::Token { n_input_tokens, .. } => n_tokens += n_input_tokens as usize,
+            Step::FloatSplit { .. } => n_tokens += 1,
             Step::Enter { .. } | Step::Exit | Step::Error { .. } => (),
         }
     }
diff --git a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
index 8498724b9ef..dd27dc48964 100644
--- a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
+++ b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
@@ -41,6 +41,39 @@ SOURCE_FILE
           SEMICOLON ";"
         WHITESPACE "\n    "
         EXPR_STMT
+          FIELD_EXPR
+            FIELD_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "x"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            NAME_REF
+              INT_NUMBER "1"
+          SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          FIELD_EXPR
+            FIELD_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "x"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            WHITESPACE " "
+            NAME_REF
+              IDENT "bar"
+          SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
           CALL_EXPR
             FIELD_EXPR
               PATH_EXPR
diff --git a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
index b8da2ddc309..98dbe45a7ec 100644
--- a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
+++ b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
@@ -1,5 +1,7 @@
 fn foo() {
     x.foo;
     x.0.bar;
+    x.0.1;
+    x.0. bar;
     x.0();
 }
diff --git a/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast b/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
index dcbcfe1231e..b28b8eb673a 100644
--- a/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
+++ b/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
@@ -58,6 +58,49 @@ SOURCE_FILE
               COMMA ","
               R_PAREN ")"
           SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          METHOD_CALL_EXPR
+            FIELD_EXPR
+              FIELD_EXPR
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "x"
+                DOT "."
+                NAME_REF
+                  INT_NUMBER "0"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            NAME_REF
+              IDENT "call"
+            ARG_LIST
+              L_PAREN "("
+              R_PAREN ")"
+          SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          METHOD_CALL_EXPR
+            FIELD_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "x"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            WHITESPACE " "
+            NAME_REF
+              IDENT "call"
+            ARG_LIST
+              L_PAREN "("
+              R_PAREN ")"
+          SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs b/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
index 1a3aa35ae8e..48bb6381e80 100644
--- a/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
+++ b/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
@@ -1,4 +1,6 @@
 fn foo() {
     x.foo();
     y.bar::<T>(1, 2,);
+    x.0.0.call();
+    x.0. call();
 }
diff --git a/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast b/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
index 9d37ada0da8..af713a22072 100644
--- a/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
+++ b/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
@@ -65,6 +65,41 @@ SOURCE_FILE
               L_PAREN "("
               R_PAREN ")"
           SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          AWAIT_EXPR
+            FIELD_EXPR
+              FIELD_EXPR
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "x"
+                DOT "."
+                NAME_REF
+                  INT_NUMBER "0"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            AWAIT_KW "await"
+          SEMICOLON ";"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          AWAIT_EXPR
+            FIELD_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "x"
+              DOT "."
+              NAME_REF
+                INT_NUMBER "0"
+            DOT "."
+            WHITESPACE " "
+            AWAIT_KW "await"
+          SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs b/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
index d2ba89ca607..fe9a3211bb1 100644
--- a/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
+++ b/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
@@ -2,4 +2,6 @@ fn foo() {
     x.await;
     x.0.await;
     x.0().await?.hello();
+    x.0.0.await;
+    x.0. await;
 }
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 467cf091787..fdc7859eb90 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -96,6 +96,7 @@ pub struct CargoConfig {
     pub target: Option<String>,
     /// Sysroot loading behavior
     pub sysroot: Option<RustcSource>,
+    pub sysroot_src: Option<AbsPathBuf>,
     /// rustc private crate source
     pub rustc_source: Option<RustcSource>,
     /// crates to disable `#[cfg(test)]` on
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index e2f09bad2de..9b6a71db811 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -25,7 +25,7 @@ mod sysroot;
 mod workspace;
 mod rustc_cfg;
 mod build_scripts;
-mod target_data_layout;
+pub mod target_data_layout;
 
 #[cfg(test)]
 mod tests;
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index e1dde12bad8..328d2fbcf31 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -76,6 +76,7 @@ impl Sysroot {
     }
 }
 
+// FIXME: Expose a builder api as loading the sysroot got way too modular and complicated.
 impl Sysroot {
     /// Attempts to discover the toolchain's sysroot from the given `dir`.
     pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
@@ -86,6 +87,16 @@ impl Sysroot {
         Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
     }
 
+    pub fn discover_with_src_override(
+        dir: &AbsPath,
+        extra_env: &FxHashMap<String, String>,
+        src: AbsPathBuf,
+    ) -> Result<Sysroot> {
+        tracing::debug!("discovering sysroot for {}", dir.display());
+        let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
+        Ok(Sysroot::load(sysroot_dir, src))
+    }
+
     pub fn discover_rustc(
         cargo_toml: &ManifestPath,
         extra_env: &FxHashMap<String, String>,
diff --git a/crates/project-model/src/target_data_layout.rs b/crates/project-model/src/target_data_layout.rs
index 267a73ac5bd..42c06ad0ed3 100644
--- a/crates/project-model/src/target_data_layout.rs
+++ b/crates/project-model/src/target_data_layout.rs
@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
 
 use crate::{utf8_stdout, ManifestPath};
 
-pub(super) fn get(
+pub fn get(
     cargo_toml: Option<&ManifestPath>,
     target: Option<&str>,
     extra_env: &FxHashMap<String, String>,
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index d784d3d0e9a..2a11f1e8eb8 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -190,8 +190,8 @@ impl ProjectWorkspace {
                 })?;
                 let cargo = CargoWorkspace::new(meta);
 
-                let sysroot = match &config.sysroot {
-                    Some(RustcSource::Path(path)) => {
+                let sysroot = match (&config.sysroot, &config.sysroot_src) {
+                    (Some(RustcSource::Path(path)), None) => {
                         match Sysroot::with_sysroot_dir(path.clone()) {
                             Ok(it) => Some(it),
                             Err(e) => {
@@ -200,7 +200,7 @@ impl ProjectWorkspace {
                             }
                         }
                     }
-                    Some(RustcSource::Discover) => {
+                    (Some(RustcSource::Discover), None) => {
                         match Sysroot::discover(cargo_toml.parent(), &config.extra_env) {
                             Ok(it) => Some(it),
                             Err(e) => {
@@ -213,8 +213,29 @@ impl ProjectWorkspace {
                             }
                         }
                     }
-                    None => None,
+                    (Some(RustcSource::Path(sysroot)), Some(sysroot_src)) => {
+                        Some(Sysroot::load(sysroot.clone(), sysroot_src.clone()))
+                    }
+                    (Some(RustcSource::Discover), Some(sysroot_src)) => {
+                        match Sysroot::discover_with_src_override(
+                            cargo_toml.parent(),
+                            &config.extra_env,
+                            sysroot_src.clone(),
+                        ) {
+                            Ok(it) => Some(it),
+                            Err(e) => {
+                                tracing::error!(
+                                    %e,
+                                    "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
+                                    cargo_toml.display()
+                                );
+                                None
+                            }
+                        }
+                    }
+                    (None, _) => None,
                 };
+
                 if let Some(sysroot) = &sysroot {
                     tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
                 }
@@ -440,9 +461,11 @@ impl ProjectWorkspace {
     /// The return type contains the path and whether or not
     /// the root is a member of the current workspace
     pub fn to_roots(&self) -> Vec<PackageRoot> {
-        let mk_sysroot = |sysroot: Option<&Sysroot>| {
+        let mk_sysroot = |sysroot: Option<&Sysroot>, project_root: Option<&AbsPath>| {
             sysroot.map(|sysroot| PackageRoot {
-                is_local: false,
+                // mark the sysroot as mutable if it is located inside of the project
+                is_local: project_root
+                    .map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
                 include: vec![sysroot.src_root().to_path_buf()],
                 exclude: Vec::new(),
             })
@@ -457,7 +480,7 @@ impl ProjectWorkspace {
                 })
                 .collect::<FxHashSet<_>>()
                 .into_iter()
-                .chain(mk_sysroot(sysroot.as_ref()))
+                .chain(mk_sysroot(sysroot.as_ref(), Some(project.path())))
                 .collect::<Vec<_>>(),
             ProjectWorkspace::Cargo {
                 cargo,
@@ -507,7 +530,7 @@ impl ProjectWorkspace {
                         }
                         PackageRoot { is_local, include, exclude }
                     })
-                    .chain(mk_sysroot(sysroot.as_ref()))
+                    .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root())))
                     .chain(rustc.iter().flat_map(|rustc| {
                         rustc.packages().map(move |krate| PackageRoot {
                             is_local: false,
@@ -524,7 +547,7 @@ impl ProjectWorkspace {
                     include: vec![detached_file.clone()],
                     exclude: Vec::new(),
                 })
-                .chain(mk_sysroot(sysroot.as_ref()))
+                .chain(mk_sysroot(sysroot.as_ref(), None))
                 .collect(),
         }
     }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8ea161dbdc4..c8075aefbbe 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -117,6 +117,11 @@ config_data! {
         ///
         /// This option does not take effect until rust-analyzer is restarted.
         cargo_sysroot: Option<String>    = "\"discover\"",
+        /// Relative path to the sysroot library sources. If left unset, this will default to
+        /// `{cargo.sysroot}/lib/rustlib/src/rust/library`.
+        ///
+        /// This option does not take effect until rust-analyzer is restarted.
+        cargo_sysrootSrc: Option<String>    = "null",
         /// Compilation target override (target triple).
         // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work
         // than `checkOnSave_target`
@@ -1103,6 +1108,8 @@ impl Config {
                 RustcSource::Path(self.root_path.join(sysroot))
             }
         });
+        let sysroot_src =
+            self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
 
         CargoConfig {
             features: match &self.data.cargo_features {
@@ -1114,6 +1121,7 @@ impl Config {
             },
             target: self.data.cargo_target.clone(),
             sysroot,
+            sysroot_src,
             rustc_source,
             unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
             wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
diff --git a/crates/tt/src/buffer.rs b/crates/tt/src/buffer.rs
index 4484431124e..0615a3763df 100644
--- a/crates/tt/src/buffer.rs
+++ b/crates/tt/src/buffer.rs
@@ -7,7 +7,12 @@ use crate::{Leaf, Subtree, TokenTree};
 struct EntryId(usize);
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
-struct EntryPtr(EntryId, usize);
+struct EntryPtr(
+    /// The index of the buffer containing the entry.
+    EntryId,
+    /// The index of the entry within the buffer.
+    usize,
+);
 
 /// Internal type which is used instead of `TokenTree` to represent a token tree
 /// within a `TokenBuffer`.
@@ -16,8 +21,8 @@ enum Entry<'t, Span> {
     // Mimicking types from proc-macro.
     Subtree(Option<&'t TokenTree<Span>>, &'t Subtree<Span>, EntryId),
     Leaf(&'t TokenTree<Span>),
-    // End entries contain a pointer to the entry from the containing
-    // token tree, or None if this is the outermost level.
+    /// End entries contain a pointer to the entry from the containing
+    /// token tree, or [`None`] if this is the outermost level.
     End(Option<EntryPtr>),
 }
 
@@ -226,8 +231,14 @@ impl<'a, Span> Cursor<'a, Span> {
     /// a cursor into that subtree
     pub fn bump_subtree(self) -> Cursor<'a, Span> {
         match self.entry() {
-            Some(Entry::Subtree(_, _, _)) => self.subtree().unwrap(),
-            _ => self.bump(),
+            Some(&Entry::Subtree(_, _, entry_id)) => {
+                Cursor::create(self.buffer, EntryPtr(entry_id, 0))
+            }
+            Some(Entry::End(exit)) => match exit {
+                Some(exit) => Cursor::create(self.buffer, *exit),
+                None => self,
+            },
+            _ => Cursor::create(self.buffer, EntryPtr(self.ptr.0, self.ptr.1 + 1)),
         }
     }
 
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 1bfb8a917a8..d5fdedfe3af 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -99,6 +99,14 @@ Unsetting this disables sysroot loading.
 
 This option does not take effect until rust-analyzer is restarted.
 --
+[[rust-analyzer.cargo.sysrootSrc]]rust-analyzer.cargo.sysrootSrc (default: `null`)::
++
+--
+Relative path to the sysroot library sources. If left unset, this will default to
+`{cargo.sysroot}/lib/rustlib/src/rust/library`.
+
+This option does not take effect until rust-analyzer is restarted.
+--
 [[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `null`)::
 +
 --
diff --git a/editors/code/language-configuration.json b/editors/code/language-configuration.json
index b1ee0843e3e..51f0e65f4fd 100644
--- a/editors/code/language-configuration.json
+++ b/editors/code/language-configuration.json
@@ -35,8 +35,8 @@
     },
     "folding": {
         "markers": {
-            "start": "^\\s*//\\s*#?region\\b",
-            "end": "^\\s*//\\s*#?endregion\\b"
+            "start": "^\\s*// region:\\b",
+            "end": "^\\s*// endregion\\b"
         }
     }
 }
diff --git a/editors/code/package.json b/editors/code/package.json
index 599e9c5a7bf..7160781b6f3 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -539,6 +539,14 @@
                         "string"
                     ]
                 },
+                "rust-analyzer.cargo.sysrootSrc": {
+                    "markdownDescription": "Relative path to the sysroot library sources. If left unset, this will default to\n`{cargo.sysroot}/lib/rustlib/src/rust/library`.\n\nThis option does not take effect until rust-analyzer is restarted.",
+                    "default": null,
+                    "type": [
+                        "null",
+                        "string"
+                    ]
+                },
                 "rust-analyzer.cargo.target": {
                     "markdownDescription": "Compilation target override (target triple).",
                     "default": null,