From b4f9fac51ab760950f2ceae27660e8f6293aa3b4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Mar 2026 09:01:13 +0100 Subject: [PATCH 01/30] =?UTF-8?q?perf:=20smol=5Fstr=20=E2=80=94=20avoid=20?= =?UTF-8?q?redundant=20work=20in=20eq=20feat:=20add=20missing=20APIs=20to?= =?UTF-8?q?=20smol=5Fstr=20docs:=20Improve=20safety=20documentation=20fix:?= =?UTF-8?q?=20borsh=20deserialization=20off-by-one=20for=20inline=20thresh?= =?UTF-8?q?old?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rust-analyzer/lib/smol_str/src/borsh.rs | 2 +- .../rust-analyzer/lib/smol_str/src/lib.rs | 138 +++++++++++++----- 2 files changed, 103 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs b/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs index b684a4910c96..944bbecbcf17 100644 --- a/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs +++ b/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs @@ -16,7 +16,7 @@ impl BorshDeserialize for SmolStr { #[inline] fn deserialize_reader(reader: &mut R) -> borsh::io::Result { let len = u32::deserialize_reader(reader)?; - if (len as usize) < INLINE_CAP { + if (len as usize) <= INLINE_CAP { let mut buf = [0u8; INLINE_CAP]; reader.read_exact(&mut buf[..len as usize])?; _ = core::str::from_utf8(&buf[..len as usize]).map_err(|err| { diff --git a/src/tools/rust-analyzer/lib/smol_str/src/lib.rs b/src/tools/rust-analyzer/lib/smol_str/src/lib.rs index 0d1f01a32b5a..b30eefc09866 100644 --- a/src/tools/rust-analyzer/lib/smol_str/src/lib.rs +++ b/src/tools/rust-analyzer/lib/smol_str/src/lib.rs @@ -100,6 +100,24 @@ pub fn is_empty(&self) -> bool { pub const fn is_heap_allocated(&self) -> bool { matches!(self.0, Repr::Heap(..)) } + + /// Constructs a `SmolStr` from a byte slice, returning an error if the slice is not valid + /// UTF-8. + #[inline] + pub fn from_utf8(bytes: &[u8]) -> Result { + core::str::from_utf8(bytes).map(SmolStr::new) + } + + /// Constructs a `SmolStr` from a byte slice without checking that the bytes are valid UTF-8. + /// + /// # Safety + /// + /// `bytes` must be valid UTF-8. + #[inline] + pub unsafe fn from_utf8_unchecked(bytes: &[u8]) -> SmolStr { + // SAFETY: caller guarantees bytes are valid UTF-8 + SmolStr::new(unsafe { core::str::from_utf8_unchecked(bytes) }) + } } impl Clone for SmolStr { @@ -116,7 +134,10 @@ fn cold_clone(v: &SmolStr) -> SmolStr { return cold_clone(self); } - // SAFETY: We verified that the payload of `Repr` is a POD + // SAFETY: The non-heap variants (`Repr::Inline` and `Repr::Static`) contain only + // `Copy` data (a `[u8; 23]` + `InlineSize` enum, or a `&'static str` fat pointer) + // and carry no drop glue, so a raw `ptr::read` bitwise copy is sound. + // The heap variant (`Repr::Heap`) is excluded above. unsafe { core::ptr::read(self as *const SmolStr) } } } @@ -142,7 +163,12 @@ fn deref(&self) -> &str { impl Eq for SmolStr {} impl PartialEq for SmolStr { fn eq(&self, other: &SmolStr) -> bool { - self.0.ptr_eq(&other.0) || self.as_str() == other.as_str() + match (&self.0, &other.0) { + (Repr::Inline { len: l_len, buf: l_buf }, Repr::Inline { len: r_len, buf: r_buf }) => { + l_len == r_len && l_buf == r_buf + } + _ => self.as_str() == other.as_str(), + } } } @@ -483,11 +509,15 @@ enum InlineSize { } impl InlineSize { - /// SAFETY: `value` must be less than or equal to [`INLINE_CAP`] + /// # Safety + /// + /// `value` must be in the range `0..=23` (i.e. a valid `InlineSize` discriminant). + /// Values outside this range would produce an invalid enum discriminant, which is UB. #[inline(always)] const unsafe fn transmute_from_u8(value: u8) -> Self { debug_assert!(value <= InlineSize::_V23 as u8); - // SAFETY: The caller is responsible to uphold this invariant + // SAFETY: The caller guarantees `value` is a valid discriminant for this + // `#[repr(u8)]` enum (0..=23), so the transmute produces a valid `InlineSize`. unsafe { mem::transmute::(value) } } } @@ -563,24 +593,15 @@ fn as_str(&self) -> &str { Repr::Static(data) => data, Repr::Inline { len, buf } => { let len = *len as usize; - // SAFETY: len is guaranteed to be <= INLINE_CAP + // SAFETY: `len` is an `InlineSize` discriminant (0..=23) which is always + // <= INLINE_CAP (23), so `..len` is always in bounds of `buf: [u8; 23]`. let buf = unsafe { buf.get_unchecked(..len) }; - // SAFETY: buf is guaranteed to be valid utf8 for ..len bytes + // SAFETY: All constructors that produce `Repr::Inline` copy from valid + // UTF-8 sources (`&str` or char encoding), so `buf[..len]` is valid UTF-8. unsafe { ::core::str::from_utf8_unchecked(buf) } } } } - - fn ptr_eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Heap(l0), Self::Heap(r0)) => Arc::ptr_eq(l0, r0), - (Self::Static(l0), Self::Static(r0)) => core::ptr::eq(l0, r0), - (Self::Inline { len: l_len, buf: l_buf }, Self::Inline { len: r_len, buf: r_buf }) => { - l_len == r_len && l_buf == r_buf - } - _ => false, - } - } } /// Convert value to [`SmolStr`] using [`fmt::Display`], potentially without allocating. @@ -666,7 +687,7 @@ fn to_ascii_lowercase_smolstr(&self) -> SmolStr { buf[..len].copy_from_slice(self.as_bytes()); buf[..len].make_ascii_lowercase(); SmolStr(Repr::Inline { - // SAFETY: `len` is in bounds + // SAFETY: `len` is guarded to be <= INLINE_CAP (23), a valid `InlineSize` discriminant. len: unsafe { InlineSize::transmute_from_u8(len as u8) }, buf, }) @@ -683,7 +704,7 @@ fn to_ascii_uppercase_smolstr(&self) -> SmolStr { buf[..len].copy_from_slice(self.as_bytes()); buf[..len].make_ascii_uppercase(); SmolStr(Repr::Inline { - // SAFETY: `len` is in bounds + // SAFETY: `len` is guarded to be <= INLINE_CAP (23), a valid `InlineSize` discriminant. len: unsafe { InlineSize::transmute_from_u8(len as u8) }, buf, }) @@ -703,8 +724,11 @@ fn replacen_smolstr(&self, from: &str, to: &str, mut count: usize) -> SmolStr { if let [from_u8] = from.as_bytes() && let [to_u8] = to.as_bytes() { + // SAFETY: `from` and `to` are single-byte `&str`s. In valid UTF-8, a single-byte + // code unit is always in the range 0x00..=0x7F (i.e. ASCII). The closure only + // replaces the matching ASCII byte with another ASCII byte, and returns all + // other bytes unchanged, so UTF-8 validity is preserved. return if self.len() <= count { - // SAFETY: `from_u8` & `to_u8` are ascii unsafe { replacen_1_ascii(self, |b| if b == from_u8 { *to_u8 } else { *b }) } } else { unsafe { @@ -736,7 +760,11 @@ fn replacen_smolstr(&self, from: &str, to: &str, mut count: usize) -> SmolStr { } } -/// SAFETY: `map` fn must only replace ascii with ascii or return unchanged bytes. +/// # Safety +/// +/// `map` must satisfy: for every byte `b` in `src`, if `b <= 0x7F` (ASCII) then `map(b)` must +/// also be `<= 0x7F` (ASCII). If `b > 0x7F` (part of a multi-byte UTF-8 sequence), `map` must +/// return `b` unchanged. This ensures the output is valid UTF-8 whenever the input is. #[inline] unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr { if src.len() <= INLINE_CAP { @@ -745,13 +773,16 @@ unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr buf[idx] = map(b); } SmolStr(Repr::Inline { - // SAFETY: `len` is in bounds + // SAFETY: `src` is a `&str` so `src.len()` <= INLINE_CAP <= 23, which is a + // valid `InlineSize` discriminant. len: unsafe { InlineSize::transmute_from_u8(src.len() as u8) }, buf, }) } else { let out = src.as_bytes().iter().map(map).collect(); - // SAFETY: We replaced ascii with ascii on valid utf8 strings. + // SAFETY: The caller guarantees `map` only substitutes ASCII bytes with ASCII + // bytes and leaves multi-byte UTF-8 continuation bytes untouched, so the + // output byte sequence is valid UTF-8. unsafe { String::from_utf8_unchecked(out).into() } } } @@ -773,9 +804,11 @@ unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr let mut is_ascii = [false; N]; while slice.len() >= N { - // SAFETY: checked in loop condition + // SAFETY: The loop condition guarantees `slice.len() >= N`, so `..N` is in bounds. let chunk = unsafe { slice.get_unchecked(..N) }; - // SAFETY: out_slice has at least same length as input slice and gets sliced with the same offsets + // SAFETY: `out_slice` starts with the same length as `slice` (both derived from + // `s.len()`) and both are advanced by the same offset `N` each iteration, so + // `out_slice.len() >= N` holds whenever `slice.len() >= N`. let out_chunk = unsafe { out_slice.get_unchecked_mut(..N) }; for j in 0..N { @@ -794,6 +827,7 @@ unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr out_chunk[j] = convert(&chunk[j]); } + // SAFETY: Same reasoning as above — both slices have len >= N at this point. slice = unsafe { slice.get_unchecked(N..) }; out_slice = unsafe { out_slice.get_unchecked_mut(N..) }; } @@ -804,7 +838,9 @@ unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr if byte > 127 { break; } - // SAFETY: out_slice has at least same length as input slice + // SAFETY: `out_slice` is always the same length as `slice` (both start equal and + // are advanced by 1 together), and `slice` is non-empty per the loop condition, + // so index 0 and `1..` are in bounds for both. unsafe { *out_slice.get_unchecked_mut(0) = convert(&byte); } @@ -813,8 +849,10 @@ unsafe fn replacen_1_ascii(src: &str, mut map: impl FnMut(&u8) -> u8) -> SmolStr } unsafe { - // SAFETY: we know this is a valid char boundary - // since we only skipped over leading ascii bytes + // SAFETY: We only advanced past bytes that satisfy `b <= 127`, i.e. ASCII bytes. + // In UTF-8, ASCII bytes (0x00..=0x7F) are always single-byte code points and + // never appear as continuation bytes, so the remaining `slice` starts at a valid + // UTF-8 char boundary. let rest = core::str::from_utf8_unchecked(slice); (out, rest) } @@ -875,9 +913,9 @@ pub const fn new() -> Self { /// Builds a [`SmolStr`] from `self`. #[must_use] - pub fn finish(&self) -> SmolStr { - SmolStr(match &self.0 { - &SmolStrBuilderRepr::Inline { len, buf } => { + pub fn finish(self) -> SmolStr { + SmolStr(match self.0 { + SmolStrBuilderRepr::Inline { len, buf } => { debug_assert!(len <= INLINE_CAP); Repr::Inline { // SAFETY: We know that `value.len` is less than or equal to the maximum value of `InlineSize` @@ -885,7 +923,7 @@ pub fn finish(&self) -> SmolStr { buf, } } - SmolStrBuilderRepr::Heap(heap) => Repr::new(heap), + SmolStrBuilderRepr::Heap(heap) => Repr::new(&heap), }) } @@ -900,8 +938,10 @@ pub fn push(&mut self, c: char) { *len += char_len; } else { let mut heap = String::with_capacity(new_len); - // copy existing inline bytes over to the heap - // SAFETY: inline data is guaranteed to be valid utf8 for `old_len` bytes + // SAFETY: `buf[..*len]` was built by prior `push`/`push_str` calls + // that only wrote valid UTF-8 (from `char::encode_utf8` or `&str` + // byte copies), so extending the Vec with these bytes preserves the + // String's UTF-8 invariant. unsafe { heap.as_mut_vec().extend_from_slice(&buf[..*len]) }; heap.push(c); self.0 = SmolStrBuilderRepr::Heap(heap); @@ -926,8 +966,10 @@ pub fn push_str(&mut self, s: &str) { let mut heap = String::with_capacity(*len); - // copy existing inline bytes over to the heap - // SAFETY: inline data is guaranteed to be valid utf8 for `old_len` bytes + // SAFETY: `buf[..old_len]` was built by prior `push`/`push_str` calls + // that only wrote valid UTF-8 (from `char::encode_utf8` or `&str` byte + // copies), so extending the Vec with these bytes preserves the String's + // UTF-8 invariant. unsafe { heap.as_mut_vec().extend_from_slice(&buf[..old_len]) }; heap.push_str(s); self.0 = SmolStrBuilderRepr::Heap(heap); @@ -945,6 +987,30 @@ fn write_str(&mut self, s: &str) -> fmt::Result { } } +impl iter::Extend for SmolStrBuilder { + fn extend>(&mut self, iter: I) { + for c in iter { + self.push(c); + } + } +} + +impl<'a> iter::Extend<&'a str> for SmolStrBuilder { + fn extend>(&mut self, iter: I) { + for s in iter { + self.push_str(s); + } + } +} + +impl<'a> iter::Extend<&'a String> for SmolStrBuilder { + fn extend>(&mut self, iter: I) { + for s in iter { + self.push_str(s); + } + } +} + impl From for SmolStr { fn from(value: SmolStrBuilder) -> Self { value.finish() From a9fe8d843ee523d8139897c586e91956c965805a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Mar 2026 09:38:38 +0100 Subject: [PATCH 02/30] Add some missing smol_str API pieces --- .../rust-analyzer/lib/smol_str/src/borsh.rs | 5 +- .../rust-analyzer/lib/smol_str/src/lib.rs | 120 +++++++++++++++++- .../rust-analyzer/lib/smol_str/src/serde.rs | 2 +- .../rust-analyzer/lib/smol_str/tests/test.rs | 24 ++++ 4 files changed, 144 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs b/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs index 944bbecbcf17..44ae513ed444 100644 --- a/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs +++ b/src/tools/rust-analyzer/lib/smol_str/src/borsh.rs @@ -29,9 +29,8 @@ fn deserialize_reader(reader: &mut R) -> borsh::io::Result { })) } else { // u8::vec_from_reader always returns Some on success in current implementation - let vec = u8::vec_from_reader(len, reader)?.ok_or_else(|| { - Error::new(ErrorKind::Other, "u8::vec_from_reader unexpectedly returned None") - })?; + let vec = u8::vec_from_reader(len, reader)? + .ok_or_else(|| Error::other("u8::vec_from_reader unexpectedly returned None"))?; Ok(SmolStr::from(String::from_utf8(vec).map_err(|err| { let msg = err.to_string(); Error::new(ErrorKind::InvalidData, msg) diff --git a/src/tools/rust-analyzer/lib/smol_str/src/lib.rs b/src/tools/rust-analyzer/lib/smol_str/src/lib.rs index b30eefc09866..55ede286c245 100644 --- a/src/tools/rust-analyzer/lib/smol_str/src/lib.rs +++ b/src/tools/rust-analyzer/lib/smol_str/src/lib.rs @@ -34,13 +34,17 @@ pub struct SmolStr(Repr); impl SmolStr { + /// The maximum byte length of a string that can be stored inline + /// without heap allocation. + pub const INLINE_CAP: usize = INLINE_CAP; + /// Constructs an inline variant of `SmolStr`. /// /// This never allocates. /// /// # Panics /// - /// Panics if `text.len() > 23`. + /// Panics if `text.len() > `[`SmolStr::INLINE_CAP`]. #[inline] pub const fn new_inline(text: &str) -> SmolStr { assert!(text.len() <= INLINE_CAP); // avoids bounds checks in loop @@ -241,6 +245,48 @@ fn partial_cmp(&self, other: &SmolStr) -> Option { } } +impl PartialOrd for SmolStr { + fn partial_cmp(&self, other: &str) -> Option { + Some(self.as_str().cmp(other)) + } +} + +impl<'a> PartialOrd<&'a str> for SmolStr { + fn partial_cmp(&self, other: &&'a str) -> Option { + Some(self.as_str().cmp(*other)) + } +} + +impl PartialOrd for &str { + fn partial_cmp(&self, other: &SmolStr) -> Option { + Some((*self).cmp(other.as_str())) + } +} + +impl PartialOrd for SmolStr { + fn partial_cmp(&self, other: &String) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &SmolStr) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl<'a> PartialOrd<&'a String> for SmolStr { + fn partial_cmp(&self, other: &&'a String) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl PartialOrd for &String { + fn partial_cmp(&self, other: &SmolStr) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + impl hash::Hash for SmolStr { fn hash(&self, hasher: &mut H) { self.as_str().hash(hasher); @@ -385,6 +431,20 @@ fn as_ref(&self) -> &std::path::Path { } } +impl From for SmolStr { + #[inline] + fn from(c: char) -> SmolStr { + let mut buf = [0; INLINE_CAP]; + let len = c.len_utf8(); + c.encode_utf8(&mut buf); + SmolStr(Repr::Inline { + // SAFETY: A char is at most 4 bytes, which is always <= INLINE_CAP (23). + len: unsafe { InlineSize::transmute_from_u8(len as u8) }, + buf, + }) + } +} + impl From<&str> for SmolStr { #[inline] fn from(s: &str) -> SmolStr { @@ -888,10 +948,18 @@ macro_rules! format_smolstr { /// A builder that can be used to efficiently build a [`SmolStr`]. /// /// This won't allocate if the final string fits into the inline buffer. -#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[derive(Clone, Default, Debug)] pub struct SmolStrBuilder(SmolStrBuilderRepr); -#[derive(Clone, Debug, PartialEq, Eq)] +impl PartialEq for SmolStrBuilder { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for SmolStrBuilder {} + +#[derive(Clone, Debug)] enum SmolStrBuilderRepr { Inline { len: usize, buf: [u8; INLINE_CAP] }, Heap(String), @@ -911,6 +979,52 @@ pub const fn new() -> Self { Self(SmolStrBuilderRepr::Inline { buf: [0; INLINE_CAP], len: 0 }) } + /// Creates a new empty [`SmolStrBuilder`] with at least the specified capacity. + /// + /// If `capacity` is less than or equal to [`SmolStr::INLINE_CAP`], the builder + /// will use inline storage and not allocate. Otherwise, it will pre-allocate a + /// heap buffer of the requested capacity. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + if capacity <= INLINE_CAP { + Self::new() + } else { + Self(SmolStrBuilderRepr::Heap(String::with_capacity(capacity))) + } + } + + /// Returns the number of bytes accumulated in the builder so far. + #[inline] + pub fn len(&self) -> usize { + match &self.0 { + SmolStrBuilderRepr::Inline { len, .. } => *len, + SmolStrBuilderRepr::Heap(heap) => heap.len(), + } + } + + /// Returns `true` if the builder has a length of zero bytes. + #[inline] + pub fn is_empty(&self) -> bool { + match &self.0 { + SmolStrBuilderRepr::Inline { len, .. } => *len == 0, + SmolStrBuilderRepr::Heap(heap) => heap.is_empty(), + } + } + + /// Returns a `&str` slice of the builder's current contents. + #[inline] + pub fn as_str(&self) -> &str { + match &self.0 { + SmolStrBuilderRepr::Inline { len, buf } => { + // SAFETY: `buf[..*len]` was built by prior `push`/`push_str` calls + // that only wrote valid UTF-8, and `*len <= INLINE_CAP` is maintained + // by the inline branch logic. + unsafe { core::str::from_utf8_unchecked(&buf[..*len]) } + } + SmolStrBuilderRepr::Heap(heap) => heap.as_str(), + } + } + /// Builds a [`SmolStr`] from `self`. #[must_use] pub fn finish(self) -> SmolStr { diff --git a/src/tools/rust-analyzer/lib/smol_str/src/serde.rs b/src/tools/rust-analyzer/lib/smol_str/src/serde.rs index 66cbcd3badc1..9d82d64805c4 100644 --- a/src/tools/rust-analyzer/lib/smol_str/src/serde.rs +++ b/src/tools/rust-analyzer/lib/smol_str/src/serde.rs @@ -16,7 +16,7 @@ fn smol_str<'de: 'a, 'a, D>(deserializer: D) -> Result impl<'a> Visitor<'a> for SmolStrVisitor { type Value = SmolStr; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a string") } diff --git a/src/tools/rust-analyzer/lib/smol_str/tests/test.rs b/src/tools/rust-analyzer/lib/smol_str/tests/test.rs index 00fab2ee1c7f..83648edeecd0 100644 --- a/src/tools/rust-analyzer/lib/smol_str/tests/test.rs +++ b/src/tools/rust-analyzer/lib/smol_str/tests/test.rs @@ -10,6 +10,7 @@ #[cfg(target_pointer_width = "64")] fn smol_str_is_smol() { assert_eq!(::std::mem::size_of::(), ::std::mem::size_of::(),); + assert_eq!(::std::mem::size_of::>(), ::std::mem::size_of::(),); } #[test] @@ -332,6 +333,29 @@ fn test_builder_push() { assert_eq!("a".repeat(24), s); } +#[test] +fn test_from_char() { + // ASCII char + let s: SmolStr = 'a'.into(); + assert_eq!(s, "a"); + assert!(!s.is_heap_allocated()); + + // Multi-byte char (2 bytes) + let s: SmolStr = SmolStr::from('ñ'); + assert_eq!(s, "ñ"); + assert!(!s.is_heap_allocated()); + + // 3-byte char + let s: SmolStr = '€'.into(); + assert_eq!(s, "€"); + assert!(!s.is_heap_allocated()); + + // 4-byte char (emoji) + let s: SmolStr = '🦀'.into(); + assert_eq!(s, "🦀"); + assert!(!s.is_heap_allocated()); +} + #[cfg(test)] mod test_str_ext { use smol_str::StrExt; From 95fcc2d5f966afa736ef88cf9be6e6e244a38e3c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 24 Feb 2026 18:29:05 +0800 Subject: [PATCH 03/30] fix: cfg_select supports non token-tree tokens - Fix single non-curly groups is ignored `true => ((),)`, old: `(),`, new: `((),)` Example --- ```rust const _: i32 = cfg_select! { true => 2 + 3, _ => 3 + 4 }; ``` **Before this PR** ```rust const _: i32 = cfg_select! { true => 2 /* expected a token tree after `=>` */+ 3, _ => 3 + 4 }; ``` **After this PR** ```rust const _: i32 = 2 + 3; ``` --- .../macro_expansion_tests/builtin_fn_macro.rs | 12 ++++++++ .../crates/hir-expand/src/builtin/fn_macro.rs | 30 +++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index eeaf865338bd..46cdb39c5b46 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -568,6 +568,12 @@ fn cfg_select() { _ => { fn true_2() {} } } +const _: ((),) = cfg_select! { _ => ((), ) }; +const _: i32 = cfg_select! { true => 2 + 3, _ => 3 + 4 }; +const _: i32 = cfg_select! { false => 2 + 3, _ => 3 + 4 }; +const _: bool = cfg_select! { _ => 2 < 3 }; +const _: bool = cfg_select! { true => foo::<(), fn() -> Foo>(1,), _ => false }; + cfg_select! { false => { fn false_3() {} } } @@ -589,6 +595,12 @@ fn true_1() {} fn true_2() {} +const _: ((),) = ((), ); +const _: i32 = 2+3; +const _: i32 = 3+4; +const _: bool = 2<3; +const _: bool = foo::<(), fn() -> Foo>(1, ); + /* error: none of the predicates in this `cfg_select` evaluated to true */ /* error: expected `=>` after cfg expression */ diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs index 949b22b0adaa..2de7290a2198 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -381,16 +381,40 @@ fn cfg_select_expand( ); } } - let expand_to_if_active = match iter.next() { - Some(tt::TtElement::Subtree(_, tt)) => tt.remaining(), - _ => { + let expand_to_if_active = match iter.peek() { + Some(tt::TtElement::Subtree(sub, tt)) if sub.delimiter.kind == DelimiterKind::Brace => { + iter.next(); + tt.remaining() + } + None | Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. }))) => { let err_span = iter.peek().map(|it| it.first_span()).unwrap_or(span); + iter.next(); return ExpandResult::new( tt::TopSubtree::empty(tt::DelimSpan::from_single(span)), ExpandError::other(err_span, "expected a token tree after `=>`"), ); } + Some(_) => { + let expr = expect_fragment( + db, + &mut iter, + parser::PrefixEntryPoint::Expr, + tt.top_subtree().delimiter.delim_span(), + ); + if let Some(err) = expr.err { + return ExpandResult::new( + tt::TopSubtree::empty(tt::DelimSpan::from_single(span)), + err.into(), + ); + } + expr.value + } }; + if let Some(TtElement::Leaf(tt::Leaf::Punct(p))) = iter.peek() + && p.char == ',' + { + iter.next(); + } if expand_to.is_none() && active { expand_to = Some(expand_to_if_active); From 4d24ed1b24d33705e5a6e9eb907e1acf0e3b5bf1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 20:27:19 +0530 Subject: [PATCH 04/30] remove make from extract_type_alias --- .../src/handlers/extract_type_alias.rs | 24 ++++++++++--------- .../src/ast/syntax_factory/constructors.rs | 8 +++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 769bbd976a26..86c2e25b2144 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -2,7 +2,7 @@ use hir::HirDisplay; use ide_db::syntax_helpers::node_ext::walk_ty; use syntax::{ - ast::{self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, make}, + ast::{self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, syntax_factory::SyntaxFactory}, syntax_editor, }; @@ -44,9 +44,9 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> let resolved_ty = if !resolved_ty.contains_unknown() { let module = ctx.sema.scope(ty.syntax())?.module(); let resolved_ty = resolved_ty.display_source_code(ctx.db(), module.into(), false).ok()?; - make::ty(&resolved_ty) + resolved_ty } else { - ty.clone() + ty.to_string() }; acc.add( @@ -54,7 +54,11 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> "Extract type as type alias", target, |builder| { + let mut edit = builder.make_editor(node); + let make = SyntaxFactory::without_mappings(); + + let resolved_ty = make.ty(&resolved_ty); let mut known_generics = match item.generic_param_list() { Some(it) => it.generic_params().collect(), @@ -68,22 +72,20 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> } let generics = collect_used_generics(&ty, &known_generics); let generic_params = - generics.map(|it| make::generic_param_list(it.into_iter().cloned())); + generics.map(|it| make.generic_param_list(it.into_iter().cloned())); // Replace original type with the alias let ty_args = generic_params.as_ref().map(|it| it.to_generic_args().generic_args()); let new_ty = if let Some(ty_args) = ty_args { - make::generic_ty_path_segment(make::name_ref("Type"), ty_args) + make.generic_ty_path_segment(make.name_ref("Type"), ty_args) } else { - make::path_segment(make::name_ref("Type")) - } - .clone_for_update(); + make.path_segment(make.name_ref("Type")) + }; edit.replace(ty.syntax(), new_ty.syntax()); // Insert new alias let ty_alias = - make::ty_alias(None, "Type", generic_params, None, None, Some((resolved_ty, None))) - .clone_for_update(); + make.ty_alias(None, "Type", generic_params, None, None, Some((resolved_ty, None))); if let Some(cap) = ctx.config.snippet_cap && let Some(name) = ty_alias.name() @@ -96,7 +98,7 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> syntax_editor::Position::before(node), vec![ ty_alias.syntax().clone().into(), - make::tokens::whitespace(&format!("\n\n{indent}")).into(), + make.whitespace(&format!("\n\n{indent}")).into(), ], ); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 27182191c3da..19d4721fe10f 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -287,6 +287,14 @@ pub fn path_segment(&self, name_ref: ast::NameRef) -> ast::PathSegment { ast } + pub fn generic_ty_path_segment( + &self, + name_ref: ast::NameRef, + generic_args: impl IntoIterator, + ) -> ast::PathSegment { + make::generic_ty_path_segment(name_ref, generic_args).clone_for_update() + } + pub fn path_segment_generics( &self, name_ref: ast::NameRef, From 9795cd922bc9261861fcbd20334bb88382dfc246 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 21:06:29 +0530 Subject: [PATCH 05/30] remove make from add_turbo_fish --- .../ide-assists/src/handlers/add_turbo_fish.rs | 13 +++++++------ .../ide-assists/src/handlers/extract_type_alias.rs | 9 +++++---- .../syntax/src/ast/syntax_factory/constructors.rs | 4 ++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs index be13b04873c8..c5e722d87e1a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -2,7 +2,7 @@ use ide_db::defs::{Definition, NameRefClass}; use syntax::{ AstNode, - ast::{self, HasArgList, HasGenericArgs, make, syntax_factory::SyntaxFactory}, + ast::{self, HasArgList, HasGenericArgs, syntax_factory::SyntaxFactory}, syntax_editor::Position, }; @@ -94,20 +94,21 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti ident.text_range(), |builder| { let mut editor = builder.make_editor(let_stmt.syntax()); + let make = SyntaxFactory::without_mappings(); if let_stmt.semicolon_token().is_none() { editor.insert( Position::last_child_of(let_stmt.syntax()), - make::tokens::semicolon(), + make.token(syntax::SyntaxKind::SEMICOLON), ); } - let placeholder_ty = make::ty_placeholder().clone_for_update(); + let placeholder_ty = make.ty_placeholder(); if let Some(pat) = let_stmt.pat() { let elements = vec![ - make::token(syntax::SyntaxKind::COLON).into(), - make::token(syntax::SyntaxKind::WHITESPACE).into(), + make.token(syntax::SyntaxKind::COLON).into(), + make.whitespace(" ").into(), placeholder_ty.syntax().clone().into(), ]; editor.insert_all(Position::after(pat.syntax()), elements); @@ -188,7 +189,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti /// This will create a turbofish generic arg list corresponding to the number of arguments fn get_fish_head(make: &SyntaxFactory, number_of_arguments: usize) -> ast::GenericArgList { - let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into()); + let args = (0..number_of_arguments).map(|_| make.type_arg(make.ty_placeholder()).into()); make.generic_arg_list(args, true) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 86c2e25b2144..e4fdac27f47f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -2,7 +2,10 @@ use hir::HirDisplay; use ide_db::syntax_helpers::node_ext::walk_ty; use syntax::{ - ast::{self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, syntax_factory::SyntaxFactory}, + ast::{ + self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, + syntax_factory::SyntaxFactory, + }, syntax_editor, }; @@ -43,8 +46,7 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> let resolved_ty = ctx.sema.resolve_type(&ty)?; let resolved_ty = if !resolved_ty.contains_unknown() { let module = ctx.sema.scope(ty.syntax())?.module(); - let resolved_ty = resolved_ty.display_source_code(ctx.db(), module.into(), false).ok()?; - resolved_ty + resolved_ty.display_source_code(ctx.db(), module.into(), false).ok()? } else { ty.to_string() }; @@ -54,7 +56,6 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> "Extract type as type alias", target, |builder| { - let mut edit = builder.make_editor(node); let make = SyntaxFactory::without_mappings(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 19d4721fe10f..159b1918059c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -295,6 +295,10 @@ pub fn generic_ty_path_segment( make::generic_ty_path_segment(name_ref, generic_args).clone_for_update() } + pub fn ty_placeholder(&self) -> ast::Type { + make::ty_placeholder().clone_for_update() + } + pub fn path_segment_generics( &self, name_ref: ast::NameRef, From 50a5d5b46dac58b70d25b8059eb4e80adc474f09 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 21:29:30 +0530 Subject: [PATCH 06/30] remove make from convert_bool_to_enum --- .../src/handlers/convert_bool_to_enum.rs | 89 +++++++++++-------- .../src/ast/syntax_factory/constructors.rs | 21 +++++ 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs index 434fbbae05c4..934779810054 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs @@ -12,9 +12,10 @@ }; use itertools::Itertools; use syntax::ast::edit::AstNodeEdit; +use syntax::ast::syntax_factory::SyntaxFactory; use syntax::{ AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, - ast::{self, HasName, edit::IndentLevel, make}, + ast::{self, HasName, edit::IndentLevel}, }; use crate::{ @@ -62,19 +63,28 @@ pub(crate) fn convert_bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) - "Convert boolean to enum", target, |edit| { + let make = SyntaxFactory::without_mappings(); if let Some(ty) = &ty_annotation { cov_mark::hit!(replaces_ty_annotation); edit.replace(ty.syntax().text_range(), "Bool"); } if let Some(initializer) = initializer { - replace_bool_expr(edit, initializer); + replace_bool_expr(edit, initializer, &make); } let usages = definition.usages(&ctx.sema).all(); - add_enum_def(edit, ctx, &usages, target_node, &target_module); + add_enum_def(edit, ctx, &usages, target_node, &target_module, &make); let mut delayed_mutations = Vec::new(); - replace_usages(edit, ctx, usages, definition, &target_module, &mut delayed_mutations); + replace_usages( + edit, + ctx, + usages, + definition, + &target_module, + &mut delayed_mutations, + &make, + ); for (scope, path) in delayed_mutations { insert_use(&scope, path, &ctx.config.insert_use); } @@ -168,16 +178,16 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { } } -fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) { +fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr, make: &SyntaxFactory) { let expr_range = expr.syntax().text_range(); - let enum_expr = bool_expr_to_enum_expr(expr); + let enum_expr = bool_expr_to_enum_expr(expr, make); edit.replace(expr_range, enum_expr.syntax().text()) } /// Converts an expression of type `bool` to one of the new enum type. -fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { - let true_expr = make::expr_path(make::path_from_text("Bool::True")); - let false_expr = make::expr_path(make::path_from_text("Bool::False")); +fn bool_expr_to_enum_expr(expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr { + let true_expr = make.expr_path(make.path_from_text("Bool::True")); + let false_expr = make.expr_path(make.path_from_text("Bool::False")); if let ast::Expr::Literal(literal) = &expr { match literal.kind() { @@ -186,10 +196,10 @@ fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { _ => expr, } } else { - make::expr_if( + make.expr_if( expr, - make::tail_only_block_expr(true_expr), - Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))), + make.tail_only_block_expr(true_expr), + Some(ast::ElseBranch::Block(make.tail_only_block_expr(false_expr))), ) .into() } @@ -203,11 +213,13 @@ fn replace_usages( target_definition: Definition, target_module: &hir::Module, delayed_mutations: &mut Vec<(ImportScope, ast::Path)>, + make: &SyntaxFactory, ) { for (file_id, references) in usages { edit.edit_file(file_id.file_id(ctx.db())); - let refs_with_imports = augment_references_with_imports(ctx, references, target_module); + let refs_with_imports = + augment_references_with_imports(ctx, references, target_module, make); refs_with_imports.into_iter().rev().for_each( |FileReferenceWithImport { range, name, import_data }| { @@ -224,12 +236,13 @@ fn replace_usages( target_definition, target_module, delayed_mutations, + make, ) } } else if let Some(initializer) = find_assignment_usage(&name) { cov_mark::hit!(replaces_assignment); - replace_bool_expr(edit, initializer); + replace_bool_expr(edit, initializer, make); } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&name) { cov_mark::hit!(replaces_negation); @@ -247,7 +260,7 @@ fn replace_usages( { cov_mark::hit!(replaces_record_expr); - let enum_expr = bool_expr_to_enum_expr(initializer); + let enum_expr = bool_expr_to_enum_expr(initializer, make); utils::replace_record_field_expr(ctx, edit, record_field, enum_expr); } else if let Some(pat) = find_record_pat_field_usage(&name) { match pat { @@ -263,6 +276,7 @@ fn replace_usages( target_definition, target_module, delayed_mutations, + make, ) } } @@ -272,14 +286,14 @@ fn replace_usages( if let Some(expr) = literal_pat.literal().and_then(|literal| { literal.syntax().ancestors().find_map(ast::Expr::cast) }) { - replace_bool_expr(edit, expr); + replace_bool_expr(edit, expr, make); } } _ => (), } } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&name) { edit.replace(ty_annotation.syntax().text_range(), "Bool"); - replace_bool_expr(edit, initializer); + replace_bool_expr(edit, initializer, make); } else if let Some(receiver) = find_method_call_expr_usage(&name) { edit.replace( receiver.syntax().text_range(), @@ -296,10 +310,10 @@ fn replace_usages( ctx, edit, record_field, - make::expr_bin_op( + make.expr_bin_op( expr, ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }), - make::expr_path(make::path_from_text("Bool::True")), + make.expr_path(make.path_from_text("Bool::True")), ), ); } else { @@ -327,6 +341,7 @@ fn augment_references_with_imports( ctx: &AssistContext<'_>, references: Vec, target_module: &hir::Module, + make: &SyntaxFactory, ) -> Vec { let mut visited_modules = FxHashSet::default(); @@ -357,9 +372,9 @@ fn augment_references_with_imports( cfg, ) .map(|mod_path| { - make::path_concat( + make.path_concat( mod_path_to_ast(&mod_path, edition), - make::path_from_text("Bool"), + make.path_from_text("Bool"), ) })?; @@ -458,6 +473,7 @@ fn add_enum_def( usages: &UsageSearchResult, target_node: SyntaxNode, target_module: &hir::Module, + make: &SyntaxFactory, ) -> Option<()> { let insert_before = node_to_insert_before(target_node); @@ -482,7 +498,7 @@ fn add_enum_def( .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module); let indent = IndentLevel::from_node(&insert_before); - let enum_def = make_bool_enum(make_enum_pub).reset_indent().indent(indent); + let enum_def = make_bool_enum(make_enum_pub, make).reset_indent().indent(indent); edit.insert( insert_before.text_range().start(), @@ -504,31 +520,30 @@ fn node_to_insert_before(target_node: SyntaxNode) -> SyntaxNode { .unwrap_or(target_node) } -fn make_bool_enum(make_pub: bool) -> ast::Enum { - let derive_eq = make::attr_outer(make::meta_token_tree( - make::ext::ident_path("derive"), - make::token_tree( +fn make_bool_enum(make_pub: bool, make: &SyntaxFactory) -> ast::Enum { + let derive_eq = make.attr_outer(make.meta_token_tree( + make.ident_path("derive"), + make.token_tree( T!['('], vec![ - NodeOrToken::Token(make::tokens::ident("PartialEq")), - NodeOrToken::Token(make::token(T![,])), - NodeOrToken::Token(make::tokens::single_space()), - NodeOrToken::Token(make::tokens::ident("Eq")), + NodeOrToken::Token(make.ident("PartialEq")), + NodeOrToken::Token(make.token(T![,])), + NodeOrToken::Token(make.whitespace(" ")), + NodeOrToken::Token(make.ident("Eq")), ], ), )); - make::enum_( + make.enum_( [derive_eq], - if make_pub { Some(make::visibility_pub()) } else { None }, - make::name("Bool"), + if make_pub { Some(make.visibility_pub()) } else { None }, + make.name("Bool"), None, None, - make::variant_list(vec![ - make::variant(None, make::name("True"), None, None), - make::variant(None, make::name("False"), None, None), + make.variant_list(vec![ + make.variant(None, make.name("True"), None, None), + make.variant(None, make.name("False"), None, None), ]), ) - .clone_for_update() } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 159b1918059c..e4aa1680ae2b 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -97,6 +97,19 @@ pub fn struct_( make::struct_(visibility, strukt_name, generic_param_list, field_list).clone_for_update() } + pub fn enum_( + &self, + attrs: impl IntoIterator, + visibility: Option, + enum_name: ast::Name, + generic_param_list: Option, + where_clause: Option, + variant_list: ast::VariantList, + ) -> ast::Enum { + make::enum_(attrs, visibility, enum_name, generic_param_list, where_clause, variant_list) + .clone_for_update() + } + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field).clone_for_update() @@ -295,6 +308,14 @@ pub fn generic_ty_path_segment( make::generic_ty_path_segment(name_ref, generic_args).clone_for_update() } + pub fn tail_only_block_expr(&self, tail_expr: ast::Expr) -> ast::BlockExpr { + make::tail_only_block_expr(tail_expr) + } + + pub fn expr_bin_op(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { + make::expr_bin_op(lhs, op, rhs) + } + pub fn ty_placeholder(&self) -> ast::Type { make::ty_placeholder().clone_for_update() } From 7edd92458629bb485e55f18dc8394a4e91c759e0 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 22:51:52 +0530 Subject: [PATCH 07/30] remove make from generate_default_from_new --- .../src/handlers/generate_default_from_new.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs index 48400d436aa6..485184723bdf 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -2,7 +2,7 @@ use stdx::format_to; use syntax::{ AstNode, - ast::{self, HasGenericParams, HasName, HasTypeBounds, Impl, make}, + ast::{self, HasGenericParams, HasName, HasTypeBounds, Impl, syntax_factory::SyntaxFactory}, }; use crate::{ @@ -72,7 +72,9 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<' let default_code = " fn default() -> Self { Self::new() }"; - let code = generate_trait_impl_text_from_impl(&impl_, self_ty, "Default", default_code); + let make = SyntaxFactory::without_mappings(); + let code = + generate_trait_impl_text_from_impl(&impl_, self_ty, "Default", default_code, &make); builder.insert(insert_location.end(), code); }, ) @@ -84,6 +86,7 @@ fn generate_trait_impl_text_from_impl( self_ty: ast::Type, trait_text: &str, code: &str, + make: &SyntaxFactory, ) -> String { let generic_params = impl_.generic_param_list().map(|generic_params| { let lifetime_params = @@ -92,18 +95,18 @@ fn generate_trait_impl_text_from_impl( // remove defaults since they can't be specified in impls let param = match param { ast::TypeOrConstParam::Type(param) => { - let param = make::type_param(param.name()?, param.type_bound_list()); + let param = make.type_param(param.name()?, param.type_bound_list()); ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { - let param = make::const_param(param.name()?, param.ty()?); + let param = make.const_param(param.name()?, param.ty()?); ast::GenericParam::ConstParam(param) } }; Some(param) }); - make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) + make.generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) }); let mut buf = String::with_capacity(code.len()); From ab42f51d1aad2f623e606b42a58c928abd861cfb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 22:58:14 +0530 Subject: [PATCH 08/30] remove make from generate_fn_type_alias_from_new --- .../src/handlers/generate_fn_type_alias.rs | 20 +++++++++---------- .../src/ast/syntax_factory/constructors.rs | 16 ++++++++++++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs index 7fd94b4bedc8..6bcbd9b0ccc2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs @@ -2,7 +2,7 @@ use ide_db::assists::{AssistId, GroupLabel}; use syntax::{ AstNode, - ast::{self, HasGenericParams, HasName, edit::IndentLevel, make}, + ast::{self, HasGenericParams, HasName, edit::IndentLevel, syntax_factory::SyntaxFactory}, syntax_editor, }; @@ -56,6 +56,7 @@ pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) func_node.syntax().text_range(), |builder| { let mut edit = builder.make_editor(func); + let make = SyntaxFactory::without_mappings(); let alias_name = format!("{}Fn", stdx::to_camel_case(&name.to_string())); @@ -68,24 +69,24 @@ pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) let is_mut = self_ty.is_mutable_reference(); if let Some(adt) = self_ty.strip_references().as_adt() { - let inner_type = make::ty(adt.name(ctx.db()).as_str()); + let inner_type = make.ty(adt.name(ctx.db()).as_str()); let ast_self_ty = - if is_ref { make::ty_ref(inner_type, is_mut) } else { inner_type }; + if is_ref { make.ty_ref(inner_type, is_mut) } else { inner_type }; - fn_params_vec.push(make::unnamed_param(ast_self_ty)); + fn_params_vec.push(make.unnamed_param(ast_self_ty)); } } fn_params_vec.extend(param_list.params().filter_map(|p| match style { ParamStyle::Named => Some(p), - ParamStyle::Unnamed => p.ty().map(make::unnamed_param), + ParamStyle::Unnamed => p.ty().map(|ty| make.unnamed_param(ty)), })); let generic_params = func_node.generic_param_list(); let is_unsafe = func_node.unsafe_token().is_some(); - let ty = make::ty_fn_ptr( + let ty = make.ty_fn_ptr( is_unsafe, func_node.abi(), fn_params_vec.into_iter(), @@ -93,22 +94,21 @@ pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) ); // Insert new alias - let ty_alias = make::ty_alias( + let ty_alias = make.ty_alias( None, &alias_name, generic_params, None, None, Some((ast::Type::FnPtrType(ty), None)), - ) - .clone_for_update(); + ); let indent = IndentLevel::from_node(insertion_node); edit.insert_all( syntax_editor::Position::before(insertion_node), vec![ ty_alias.syntax().clone().into(), - make::tokens::whitespace(&format!("\n\n{indent}")).into(), + make.whitespace(&format!("\n\n{indent}")).into(), ], ); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index e4aa1680ae2b..1601ff3174a7 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -3,7 +3,7 @@ AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, ast::{ self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName, - HasTypeBounds, HasVisibility, RangeItem, make, + HasTypeBounds, HasVisibility, Param, RangeItem, make, }, syntax_editor::SyntaxMappingBuilder, }; @@ -110,6 +110,20 @@ pub fn enum_( .clone_for_update() } + pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param { + make::unnamed_param(ty).clone_for_update() + } + + pub fn ty_fn_ptr>( + &self, + is_unsafe: bool, + abi: Option, + params: I, + ret_type: Option, + ) -> ast::FnPtrType { + make::ty_fn_ptr(is_unsafe, abi, params, ret_type).clone_for_update() + } + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field).clone_for_update() From fa86ca068713378b6feb494ff63631647d32843b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 23:22:37 +0530 Subject: [PATCH 09/30] remove make from move_bound --- .../ide-assists/src/handlers/move_bounds.rs | 15 +++++++-------- .../syntax/src/ast/syntax_factory/constructors.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs index e5425abab097..e9b77a729438 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs @@ -3,7 +3,7 @@ ast::{ self, AstNode, HasName, HasTypeBounds, edit_in_place::{GenericParamsOwnerEdit, Removable}, - make, + syntax_factory::SyntaxFactory, }, match_ast, }; @@ -50,6 +50,7 @@ pub(crate) fn move_bounds_to_where_clause( |edit| { let type_param_list = edit.make_mut(type_param_list); let parent = edit.make_syntax_mut(parent); + let make = SyntaxFactory::without_mappings(); let where_clause: ast::WhereClause = match_ast! { match parent { @@ -70,7 +71,7 @@ pub(crate) fn move_bounds_to_where_clause( ast::GenericParam::ConstParam(_) => continue, }; if let Some(tbl) = param.type_bound_list() { - if let Some(predicate) = build_predicate(generic_param) { + if let Some(predicate) = build_predicate(generic_param, &make) { where_clause.add_predicate(predicate) } tbl.remove() @@ -80,15 +81,13 @@ pub(crate) fn move_bounds_to_where_clause( ) } -fn build_predicate(param: ast::GenericParam) -> Option { +fn build_predicate(param: ast::GenericParam, make: &SyntaxFactory) -> Option { let target = match ¶m { - ast::GenericParam::TypeParam(t) => { - Either::Right(make::ty_path(make::ext::ident_path(&t.name()?.to_string()))) - } + ast::GenericParam::TypeParam(t) => Either::Right(make.ty(&t.name()?.to_string())), ast::GenericParam::LifetimeParam(l) => Either::Left(l.lifetime()?), ast::GenericParam::ConstParam(_) => return None, }; - let predicate = make::where_pred( + let predicate = make.where_pred( target, match param { ast::GenericParam::TypeParam(t) => t.type_bound_list()?, @@ -97,7 +96,7 @@ fn build_predicate(param: ast::GenericParam) -> Option { } .bounds(), ); - Some(predicate.clone_for_update()) + Some(predicate) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 1601ff3174a7..f50cd90ce9d0 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1,4 +1,6 @@ //! Wrappers over [`make`] constructors +use either::Either; + use crate::{ AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, ast::{ @@ -124,6 +126,14 @@ pub fn ty_fn_ptr>( make::ty_fn_ptr(is_unsafe, abi, params, ret_type).clone_for_update() } + pub fn where_pred( + &self, + path: Either, + bounds: impl IntoIterator, + ) -> ast::WherePred { + make::where_pred(path, bounds).clone_for_update() + } + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field).clone_for_update() From 2726293132bdbd7a2847fe4a1d93f90f56062f56 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 23:40:10 +0530 Subject: [PATCH 10/30] migrate move bounds to SyntaxEditor API --- .../ide-assists/src/handlers/move_bounds.rs | 85 ++++++++++++++----- .../src/ast/syntax_factory/constructors.rs | 7 ++ 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs index e9b77a729438..01a46c33346a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs @@ -1,11 +1,8 @@ use either::Either; use syntax::{ - ast::{ - self, AstNode, HasName, HasTypeBounds, - edit_in_place::{GenericParamsOwnerEdit, Removable}, - syntax_factory::SyntaxFactory, - }, + ast::{self, AstNode, HasGenericParams, HasName, HasTypeBounds, syntax_factory::SyntaxFactory}, match_ast, + syntax_editor::{Position, Removable}, }; use crate::{AssistContext, AssistId, Assists}; @@ -47,23 +44,70 @@ pub(crate) fn move_bounds_to_where_clause( AssistId::refactor_rewrite("move_bounds_to_where_clause"), "Move to where clause", target, - |edit| { - let type_param_list = edit.make_mut(type_param_list); - let parent = edit.make_syntax_mut(parent); + |builder| { + let mut edit = builder.make_editor(&parent); let make = SyntaxFactory::without_mappings(); - let where_clause: ast::WhereClause = match_ast! { - match parent { - ast::Fn(it) => it.get_or_create_where_clause(), - ast::Trait(it) => it.get_or_create_where_clause(), - ast::Impl(it) => it.get_or_create_where_clause(), - ast::Enum(it) => it.get_or_create_where_clause(), - ast::Struct(it) => it.get_or_create_where_clause(), - ast::TypeAlias(it) => it.get_or_create_where_clause(), - _ => return, + let new_preds: Vec = type_param_list + .generic_params() + .filter_map(|param| build_predicate(param, &make)) + .collect(); + + let existing_where: Option = match_ast! { + match (&parent) { + ast::Fn(it) => it.where_clause(), + ast::Trait(it) => it.where_clause(), + ast::Impl(it) => it.where_clause(), + ast::Enum(it) => it.where_clause(), + ast::Struct(it) => it.where_clause(), + ast::TypeAlias(it) => it.where_clause(), + _ => None, } }; + let all_preds = existing_where.iter().flat_map(|wc| wc.predicates()).chain(new_preds); + let new_where = make.where_clause(all_preds); + + if let Some(existing) = &existing_where { + edit.replace(existing.syntax(), new_where.syntax()); + } else { + let pos: Option = match_ast! { + match (&parent) { + ast::Fn(it) => it.ret_type() + .map(|t| Position::after(t.syntax())) + .or_else(|| it.param_list().map(|t| Position::after(t.syntax()))), + ast::Trait(it) => it.generic_param_list() + .map(|t| Position::after(t.syntax())) + .or_else(|| it.name().map(|t| Position::after(t.syntax()))), + ast::Impl(it) => it.self_ty() + .map(|t| Position::after(t.syntax())), + ast::Enum(it) => it.generic_param_list() + .map(|t| Position::after(t.syntax())) + .or_else(|| it.name().map(|t| Position::after(t.syntax()))), + ast::Struct(it) => it.field_list() + .and_then(|fl| match fl { + ast::FieldList::TupleFieldList(it) => { + Some(Position::after(it.syntax())) + } + ast::FieldList::RecordFieldList(_) => None, + }) + .or_else(|| it.generic_param_list() + .map(|t| Position::after(t.syntax()))) + .or_else(|| it.name().map(|t| Position::after(t.syntax()))), + ast::TypeAlias(it) => it.generic_param_list() + .map(|t| Position::after(t.syntax())) + .or_else(|| it.name().map(|t| Position::after(t.syntax()))), + _ => None, + } + }; + if let Some(pos) = pos { + edit.insert_all( + pos, + vec![make.whitespace(" ").into(), new_where.syntax().clone().into()], + ); + } + } + for generic_param in type_param_list.generic_params() { let param: &dyn HasTypeBounds = match &generic_param { ast::GenericParam::TypeParam(t) => t, @@ -71,12 +115,11 @@ pub(crate) fn move_bounds_to_where_clause( ast::GenericParam::ConstParam(_) => continue, }; if let Some(tbl) = param.type_bound_list() { - if let Some(predicate) = build_predicate(generic_param, &make) { - where_clause.add_predicate(predicate) - } - tbl.remove() + tbl.remove(&mut edit); } } + + builder.add_file_edits(ctx.vfs_file_id(), edit); }, ) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index f50cd90ce9d0..40412c773334 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -134,6 +134,13 @@ pub fn where_pred( make::where_pred(path, bounds).clone_for_update() } + pub fn where_clause( + &self, + predicates: impl IntoIterator, + ) -> ast::WhereClause { + make::where_clause(predicates).clone_for_update() + } + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field).clone_for_update() From 3234fad9352cce63bcbfd30ac47fd7ffa26cad90 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 2 Mar 2026 23:46:46 +0530 Subject: [PATCH 11/30] remove make from replace_named_generics_with_impl --- .../handlers/replace_named_generic_with_impl.rs | 15 +++++++-------- .../syntax/src/ast/syntax_factory/constructors.rs | 4 ++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs index df7057835c34..018642a04723 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs @@ -5,9 +5,10 @@ defs::Definition, search::{SearchScope, UsageSearchResult}, }; +use syntax::ast::syntax_factory::SyntaxFactory; use syntax::{ AstNode, - ast::{self, HasGenericParams, HasName, HasTypeBounds, Name, NameLike, PathType, make}, + ast::{self, HasGenericParams, HasName, HasTypeBounds, Name, NameLike, PathType}, match_ast, }; @@ -72,6 +73,7 @@ pub(crate) fn replace_named_generic_with_impl( target, |edit| { let mut editor = edit.make_editor(type_param.syntax()); + let make = SyntaxFactory::without_mappings(); // remove trait from generic param list if let Some(generic_params) = fn_.generic_param_list() { @@ -83,17 +85,14 @@ pub(crate) fn replace_named_generic_with_impl( if params.is_empty() { editor.delete(generic_params.syntax()); } else { - let new_generic_param_list = make::generic_param_list(params); - editor.replace( - generic_params.syntax(), - new_generic_param_list.syntax().clone_for_update(), - ); + let new_generic_param_list = make.generic_param_list(params); + editor.replace(generic_params.syntax(), new_generic_param_list.syntax()); } } - let new_bounds = make::impl_trait_type(type_bound_list); + let new_bounds = make.impl_trait_type(type_bound_list); for path_type in path_types_to_replace.iter().rev() { - editor.replace(path_type.syntax(), new_bounds.clone_for_update().syntax()); + editor.replace(path_type.syntax(), new_bounds.syntax()); } edit.add_file_edits(ctx.vfs_file_id(), editor); }, diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 40412c773334..50fe56538080 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -141,6 +141,10 @@ pub fn where_clause( make::where_clause(predicates).clone_for_update() } + pub fn impl_trait_type(&self, bounds: ast::TypeBoundList) -> ast::ImplTraitType { + make::impl_trait_type(bounds).clone_for_update() + } + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field).clone_for_update() From 6a653172f5049c16d266245a8a1967953c1e3139 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 6 Mar 2026 06:04:10 +0530 Subject: [PATCH 12/30] add GetOrCreateWhereClause subtrait in edit to provide get_or_create_where_clause --- .../ide-assists/src/handlers/move_bounds.rs | 63 ++--------- .../crates/syntax/src/syntax_editor.rs | 2 +- .../crates/syntax/src/syntax_editor/edits.rs | 101 ++++++++++++++++++ 3 files changed, 112 insertions(+), 54 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs index 01a46c33346a..79b8bd5d3d48 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs @@ -1,8 +1,8 @@ use either::Either; use syntax::{ - ast::{self, AstNode, HasGenericParams, HasName, HasTypeBounds, syntax_factory::SyntaxFactory}, + ast::{self, AstNode, HasName, HasTypeBounds, syntax_factory::SyntaxFactory}, match_ast, - syntax_editor::{Position, Removable}, + syntax_editor::{GetOrCreateWhereClause, Removable}, }; use crate::{AssistContext, AssistId, Assists}; @@ -53,61 +53,18 @@ pub(crate) fn move_bounds_to_where_clause( .filter_map(|param| build_predicate(param, &make)) .collect(); - let existing_where: Option = match_ast! { + match_ast! { match (&parent) { - ast::Fn(it) => it.where_clause(), - ast::Trait(it) => it.where_clause(), - ast::Impl(it) => it.where_clause(), - ast::Enum(it) => it.where_clause(), - ast::Struct(it) => it.where_clause(), - ast::TypeAlias(it) => it.where_clause(), - _ => None, + ast::Fn(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + ast::Trait(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + ast::Impl(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + ast::Enum(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + ast::Struct(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + ast::TypeAlias(it) => it.get_or_create_where_clause(&mut edit, &make, new_preds.into_iter()), + _ => return, } }; - let all_preds = existing_where.iter().flat_map(|wc| wc.predicates()).chain(new_preds); - let new_where = make.where_clause(all_preds); - - if let Some(existing) = &existing_where { - edit.replace(existing.syntax(), new_where.syntax()); - } else { - let pos: Option = match_ast! { - match (&parent) { - ast::Fn(it) => it.ret_type() - .map(|t| Position::after(t.syntax())) - .or_else(|| it.param_list().map(|t| Position::after(t.syntax()))), - ast::Trait(it) => it.generic_param_list() - .map(|t| Position::after(t.syntax())) - .or_else(|| it.name().map(|t| Position::after(t.syntax()))), - ast::Impl(it) => it.self_ty() - .map(|t| Position::after(t.syntax())), - ast::Enum(it) => it.generic_param_list() - .map(|t| Position::after(t.syntax())) - .or_else(|| it.name().map(|t| Position::after(t.syntax()))), - ast::Struct(it) => it.field_list() - .and_then(|fl| match fl { - ast::FieldList::TupleFieldList(it) => { - Some(Position::after(it.syntax())) - } - ast::FieldList::RecordFieldList(_) => None, - }) - .or_else(|| it.generic_param_list() - .map(|t| Position::after(t.syntax()))) - .or_else(|| it.name().map(|t| Position::after(t.syntax()))), - ast::TypeAlias(it) => it.generic_param_list() - .map(|t| Position::after(t.syntax())) - .or_else(|| it.name().map(|t| Position::after(t.syntax()))), - _ => None, - } - }; - if let Some(pos) = pos { - edit.insert_all( - pos, - vec![make.whitespace(" ").into(), new_where.syntax().clone().into()], - ); - } - } - for generic_param in type_param_list.generic_params() { let param: &dyn HasTypeBounds = match &generic_param { ast::GenericParam::TypeParam(t) => t, diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index 5683d891be7a..e6937e4d0f8a 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -20,7 +20,7 @@ mod edits; mod mapping; -pub use edits::Removable; +pub use edits::{GetOrCreateWhereClause, Removable}; pub use mapping::{SyntaxMapping, SyntaxMappingBuilder}; #[derive(Debug)] diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index 9090f7c9eb14..ad08928923be 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -10,6 +10,107 @@ syntax_editor::{Position, SyntaxEditor}, }; +pub trait GetOrCreateWhereClause: ast::HasGenericParams { + fn where_clause_position(&self) -> Option; + + fn get_or_create_where_clause( + &self, + editor: &mut SyntaxEditor, + make: &SyntaxFactory, + new_preds: impl Iterator, + ) { + let existing = self.where_clause(); + let all_preds: Vec<_> = + existing.iter().flat_map(|wc| wc.predicates()).chain(new_preds).collect(); + let new_where = make.where_clause(all_preds); + + if let Some(existing) = &existing { + editor.replace(existing.syntax(), new_where.syntax()); + } else if let Some(pos) = self.where_clause_position() { + editor.insert_all( + pos, + vec![make.whitespace(" ").into(), new_where.syntax().clone().into()], + ); + } + } +} + +impl GetOrCreateWhereClause for ast::Fn { + fn where_clause_position(&self) -> Option { + if let Some(ty) = self.ret_type() { + Some(Position::after(ty.syntax())) + } else if let Some(param_list) = self.param_list() { + Some(Position::after(param_list.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + +impl GetOrCreateWhereClause for ast::Impl { + fn where_clause_position(&self) -> Option { + if let Some(ty) = self.self_ty() { + Some(Position::after(ty.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + +impl GetOrCreateWhereClause for ast::Trait { + fn where_clause_position(&self) -> Option { + if let Some(gpl) = self.generic_param_list() { + Some(Position::after(gpl.syntax())) + } else if let Some(name) = self.name() { + Some(Position::after(name.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + +impl GetOrCreateWhereClause for ast::TypeAlias { + fn where_clause_position(&self) -> Option { + if let Some(gpl) = self.generic_param_list() { + Some(Position::after(gpl.syntax())) + } else if let Some(name) = self.name() { + Some(Position::after(name.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + +impl GetOrCreateWhereClause for ast::Struct { + fn where_clause_position(&self) -> Option { + let tfl = self.field_list().and_then(|fl| match fl { + ast::FieldList::RecordFieldList(_) => None, + ast::FieldList::TupleFieldList(it) => Some(it), + }); + if let Some(tfl) = tfl { + Some(Position::after(tfl.syntax())) + } else if let Some(gpl) = self.generic_param_list() { + Some(Position::after(gpl.syntax())) + } else if let Some(name) = self.name() { + Some(Position::after(name.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + +impl GetOrCreateWhereClause for ast::Enum { + fn where_clause_position(&self) -> Option { + if let Some(gpl) = self.generic_param_list() { + Some(Position::after(gpl.syntax())) + } else if let Some(name) = self.name() { + Some(Position::after(name.syntax())) + } else { + Some(Position::last_child_of(self.syntax())) + } + } +} + impl SyntaxEditor { /// Adds a new generic param to the function using `SyntaxEditor` pub fn add_generic_param(&mut self, function: &Fn, new_param: GenericParam) { From ddb729abdc8fb0bc47e4dcb95b83cef050ef6272 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Mar 2026 13:38:25 +0100 Subject: [PATCH 13/30] fix: Fix invalid `PartialEq` impl for `EditionedFileIdData` This broke the contract of `HashEqLike`, causing interning to return different IDs --- .../crates/base-db/src/editioned_file_id.rs | 14 +++++++++++++- .../hir-def/src/nameres/tests/incremental.rs | 1 - .../crates/hir-ty/src/tests/macros.rs | 10 +++++----- .../rust-analyzer/crates/ide-db/src/traits.rs | 5 ++++- .../rust-analyzer/crates/ide/src/typing.rs | 18 ++++++++---------- .../crates/test-fixture/src/lib.rs | 13 +++++++++---- 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs index 13fb05d56547..062e3b59d941 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs @@ -20,7 +20,7 @@ pub struct EditionedFileId( use zalsa_::interned as zalsa_struct_; type Configuration_ = EditionedFileId; - #[derive(Debug, Clone, PartialEq, Eq)] + #[derive(Debug, Clone, Eq)] pub struct EditionedFileIdData { editioned_file_id: span::EditionedFileId, krate: Crate, @@ -52,6 +52,14 @@ struct WithoutCrate { editioned_file_id: span::EditionedFileId, } + impl PartialEq for EditionedFileIdData { + fn eq(&self, other: &Self) -> bool { + let Self { editioned_file_id, krate: _ } = self; + let Self { editioned_file_id: other_editioned_file_id, krate: _ } = other; + editioned_file_id == other_editioned_file_id + } + } + impl Hash for EditionedFileIdData { #[inline] fn hash(&self, state: &mut H) { @@ -210,6 +218,8 @@ pub fn from_span( /// 1. The file is not in the module tree. /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin /// (e.g. on enter feature, folding, etc.). + // FIXME: Remove this and all the weird crate ignoring plumbing around this + // This can cause a variety of weird bugs https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Broken.20token.20mapping/with/577739887 pub fn from_span_guess_origin( db: &dyn RootQueryDb, editioned_file_id: span::EditionedFileId, @@ -281,6 +291,8 @@ pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition, krate: C /// 1. The file is not in the module tree. /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin /// (e.g. on enter feature, folding, etc.). + // FIXME: Remove this and all the weird crate ignoring plumbing around this + // This can cause a variety of weird bugs https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Broken.20token.20mapping/with/577739887 #[inline] pub fn current_edition_guess_origin(db: &dyn RootQueryDb, file_id: FileId) -> Self { Self::from_span_guess_origin(db, span::EditionedFileId::current_edition(file_id)) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 225ba958634e..b2a056ff2a60 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -118,7 +118,6 @@ pub fn foo() {} expect![[r#" [ "crate_local_def_map", - "file_item_tree_query", "crate_local_def_map", ] "#]], diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs index 2f41de64cbbd..28a688d4a39b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs @@ -9,16 +9,16 @@ fn cfg_impl_def() { check_types( r#" -//- /main.rs crate:main deps:foo cfg:test +//- /main.rs crate:main deps:foo cfg:some_cfg use foo::S as T; struct S; -#[cfg(test)] +#[cfg(some_cfg)] impl S { fn foo1(&self) -> i32 { 0 } } -#[cfg(not(test))] +#[cfg(not(some_cfg))] impl S { fn foo2(&self) -> i32 { 0 } } @@ -31,12 +31,12 @@ fn test() { //- /foo.rs crate:foo pub struct S; -#[cfg(not(test))] +#[cfg(not(some_cfg))] impl S { pub fn foo3(&self) -> i32 { 0 } } -#[cfg(test)] +#[cfg(some_cfg)] impl S { pub fn foo4(&self) -> i32 { 0 } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/traits.rs b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs index 7200e7fbe5ae..41ef50165383 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/traits.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs @@ -113,6 +113,7 @@ fn assoc_item_of_trait( #[cfg(test)] mod tests { + use base_db::RootQueryDb; use expect_test::{Expect, expect}; use hir::{EditionedFileId, FilePosition, Semantics}; use span::Edition; @@ -130,7 +131,9 @@ pub(crate) fn position( database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); - let file_id = EditionedFileId::from_span_guess_origin(&database, file_id); + + let &krate = database.relevant_crates(file_id.file_id()).first().unwrap(); + let file_id = EditionedFileId::from_span(&database, file_id, krate); let offset = range_or_offset.expect_offset(); (database, FilePosition { file_id, offset }) } diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index f8b0dbfe6282..ca2194fceb67 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -17,10 +17,7 @@ use either::Either; use hir::EditionedFileId; -use ide_db::{ - FilePosition, RootDatabase, - base_db::{RootQueryDb, SourceDatabase}, -}; +use ide_db::{FilePosition, RootDatabase, base_db::RootQueryDb}; use span::Edition; use std::iter; @@ -73,15 +70,16 @@ pub(crate) fn on_char_typed( if !TRIGGER_CHARS.contains(&char_typed) { return None; } - let edition = db - .source_root_crates(db.file_source_root(position.file_id).source_root_id(db)) + let krate = db + .relevant_crates(position.file_id) .first() - .map_or(Edition::CURRENT, |crates| crates.data(db).edition); - // FIXME: We are hitting the database here, if we are unlucky this call might block momentarily - // causing the editor to feel sluggish! We need to make this bail if it would block too long? - let editioned_file_id_wrapper = EditionedFileId::from_span_guess_origin( + .copied() + .unwrap_or_else(|| *db.all_crates().first().unwrap()); + let edition = krate.data(db).edition; + let editioned_file_id_wrapper = EditionedFileId::from_span( db, span::EditionedFileId::new(position.file_id, edition), + krate, ); let file = &db.parse(editioned_file_id_wrapper); let char_matches_position = diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index ca68edd88c05..93d4650cd3c4 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -149,8 +149,9 @@ fn with_single_file( let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture"); - let file = EditionedFileId::from_span_guess_origin(&db, fixture.files[0]); - (db, file) + let &krate = db.relevant_crates(fixture.files[0].file_id()).first().unwrap(); + let file_id = EditionedFileId::from_span(&db, fixture.files[0], krate); + (db, file_id) } /// See the trait documentation for more information on fixtures. @@ -165,7 +166,10 @@ fn with_many_files( let files = fixture .files .into_iter() - .map(|file| EditionedFileId::from_span_guess_origin(&db, file)) + .map(|file| { + let &krate = db.relevant_crates(file.file_id()).first().unwrap(); + EditionedFileId::from_span(&db, file, krate) + }) .collect(); (db, files) } @@ -222,7 +226,8 @@ fn with_range_or_offset( let (file_id, range_or_offset) = fixture .file_position .expect("Could not find file position in fixture. Did you forget to add an `$0`?"); - let file_id = EditionedFileId::from_span_guess_origin(&db, file_id); + let &krate = db.relevant_crates(file_id.file_id()).first().unwrap(); + let file_id = EditionedFileId::from_span(&db, file_id, krate); (db, file_id, range_or_offset) } From ae909778390ad9af2f919e171a576e0f9211d083 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 6 Mar 2026 15:43:14 +0000 Subject: [PATCH 14/30] fix: Update VFS when a watched file is deleted When rust-analyzer handles file watching, it previously wouldn't update the VFS on file deletion. We would discard events where fs::metadata() isn't available, and we never have metadata for deleted files. See also the discussion in rust-lang/rust-analyzer#19907. --- .../crates/vfs-notify/src/lib.rs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs index c6393cc6922a..f55ce24597b7 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs @@ -208,6 +208,22 @@ fn run(mut self, inbox: Receiver) { ) }) .filter_map(|path| -> Option<(AbsPathBuf, Option>)> { + // Ignore events for files/directories that we're not watching. + if !(self.watched_file_entries.contains(&path) + || self + .watched_dir_entries + .iter() + .any(|dir| dir.contains_file(&path))) + { + return None; + } + + // For removed files, fs::metadata() will return Err, but + // we still want to update the VFS. + if matches!(event.kind, EventKind::Remove(_)) { + return Some((path, None)); + } + let meta = fs::metadata(&path).ok()?; if meta.file_type().is_dir() && self @@ -223,15 +239,6 @@ fn run(mut self, inbox: Receiver) { return None; } - if !(self.watched_file_entries.contains(&path) - || self - .watched_dir_entries - .iter() - .any(|dir| dir.contains_file(&path))) - { - return None; - } - let contents = read(&path); Some((path, contents)) }) From 56a074bd3567032ef4ee2433d658d78aa727b596 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 6 Mar 2026 17:09:01 +0000 Subject: [PATCH 15/30] fix: File watcher should watch directories recursively Currently rust-analyzer only watches the directory itself, and doesn't consider children recursively. This is a problem when the directory itself is deleted and recreated (e.g. if you're creating all of `mycrate/src/` with a code generating script). The obvious solution is to configure rust-analyzer to watch the parent directory (assuming rust-project.json), but that requires recursive watching. This problem probably also occurs when switching between git commits. Instead, watch directories recursively so we can use the file watcher with parent directories. See also discussion on rust-lang/rust-analyzer#19907. I've tested on some decent sized projects (several hundred transitive dependencies) and performance seemed fine. --- src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs index c6393cc6922a..3214c03a0eab 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs @@ -317,7 +317,7 @@ fn load_entry( fn watch(&mut self, path: &Path) { if let Some((watcher, _)) = &mut self.watcher { - log_notify_error(watcher.watch(path, RecursiveMode::NonRecursive)); + log_notify_error(watcher.watch(path, RecursiveMode::Recursive)); } } From 65e7758f07a2d5b14c4be67e4bc8dd9281f76a2d Mon Sep 17 00:00:00 2001 From: Jason Haslam Date: Fri, 6 Mar 2026 11:35:35 -0700 Subject: [PATCH 16/30] Fix syntax/fuzz build error --- src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml index b2f238efc025..41db3ddcc550 100644 --- a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml @@ -23,6 +23,3 @@ path = "fuzz_targets/parser.rs" [[bin]] name = "reparse" path = "fuzz_targets/reparse.rs" - -[lints] -workspace = true From ecec8315572cd7992b692ea15bd9d083bc7ec51a Mon Sep 17 00:00:00 2001 From: protonblu Date: Sat, 7 Mar 2026 12:44:36 +0530 Subject: [PATCH 17/30] Migrate unqualify_method_call assist to SyntaxEditor --- .../src/handlers/unqualify_method_call.rs | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs index a58b1da621c7..ef395791e251 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs @@ -1,8 +1,5 @@ use hir::AsAssocItem; -use syntax::{ - TextRange, - ast::{self, AstNode, HasArgList, prec::ExprPrecedence}, -}; +use syntax::ast::{self, AstNode, HasArgList, prec::ExprPrecedence, syntax_factory::SyntaxFactory}; use crate::{AssistContext, AssistId, Assists}; @@ -36,10 +33,7 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) } let args = call.arg_list()?; - let l_paren = args.l_paren_token()?; - let mut args_iter = args.args(); - let first_arg = args_iter.next()?; - let second_arg = args_iter.next(); + let first_arg = args.args().next()?; let qualifier = path.qualifier()?; let method_name = path.segment()?.name_ref()?; @@ -51,43 +45,33 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) return None; } - // `core::ops::Add::add(` -> `` - let delete_path = - TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end()); - - // Parens around `expr` if needed - let parens = first_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix).then(|| { - let range = first_arg.syntax().text_range(); - (range.start(), range.end()) - }); - - // `, ` -> `.add(` - let replace_comma = TextRange::new( - first_arg.syntax().text_range().end(), - second_arg - .map(|a| a.syntax().text_range().start()) - .unwrap_or_else(|| first_arg.syntax().text_range().end()), - ); - acc.add( AssistId::refactor_rewrite("unqualify_method_call"), "Unqualify method call", call.syntax().text_range(), - |edit| { - edit.delete(delete_path); - if let Some((open, close)) = parens { - edit.insert(open, "("); - edit.insert(close, ")"); - } - edit.replace(replace_comma, format!(".{method_name}(")); + |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(call.syntax()); + + let new_arg_list = make.arg_list(args.args().skip(1)); + let receiver = if first_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix) { + ast::Expr::from(make.expr_paren(first_arg.clone())) + } else { + first_arg.clone() + }; + let method_call = make.expr_method_call(receiver, method_name, new_arg_list); + + editor.replace(call.syntax(), method_call.syntax()); if let Some(fun) = fun.as_assoc_item(ctx.db()) && let Some(trait_) = fun.container_or_implemented_trait(ctx.db()) && !scope.can_use_trait_methods(trait_) { - // Only add an import for trait methods that are not already imported. - add_import(qualifier, ctx, edit); + add_import(qualifier, ctx, &make, &mut editor); } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -95,7 +79,8 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) fn add_import( qualifier: ast::Path, ctx: &AssistContext<'_>, - edit: &mut ide_db::source_change::SourceChangeBuilder, + make: &SyntaxFactory, + editor: &mut syntax::syntax_editor::SyntaxEditor, ) { if let Some(path_segment) = qualifier.segment() { // for `` @@ -122,8 +107,13 @@ fn add_import( ); if let Some(scope) = scope { - let scope = edit.make_import_scope_mut(scope); - ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use); + ide_db::imports::insert_use::insert_use_with_editor( + &scope, + import, + &ctx.config.insert_use, + editor, + make, + ); } } } From 9d5b5f8a022e6422529348c832ab0feb14a3447b Mon Sep 17 00:00:00 2001 From: Divyesh Date: Sun, 8 Mar 2026 01:22:02 +0530 Subject: [PATCH 18/30] ide-assists: remove unnecessary clones in two assists --- .../crates/ide-assists/src/handlers/convert_bool_then.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index d2c4ed9b5a2c..236436989e15 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -102,11 +102,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> ast::Expr::BlockExpr(block) => unwrap_trivial_block(block), e => e, }; - let cond = if invert_cond { - invert_boolean_expression(&make, cond) - } else { - cond.clone_for_update() - }; + let cond = if invert_cond { invert_boolean_expression(&make, cond) } else { cond }; let parenthesize = matches!( cond, From a1a242a54bf414c03a8602fdcdf346970549c9fc Mon Sep 17 00:00:00 2001 From: Jaroslaw Roszyk Date: Sun, 8 Mar 2026 02:06:45 +0100 Subject: [PATCH 19/30] feat: add --json flag to rust-analyzer parse --- .../crates/rust-analyzer/src/cli/flags.rs | 5 +- .../crates/rust-analyzer/src/cli/parse.rs | 87 ++++++++++++++++++- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs index c52206018138..d68f7ab5b7b2 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs @@ -40,6 +40,8 @@ cmd parse { /// Suppress printing. optional --no-dump + /// Output as JSON. + optional --json } /// Parse stdin and print the list of symbols. @@ -233,6 +235,7 @@ pub struct LspServer { #[derive(Debug)] pub struct Parse { pub no_dump: bool, + pub json: bool, } #[derive(Debug)] @@ -257,8 +260,8 @@ pub struct AnalysisStats { pub disable_build_scripts: bool, pub disable_proc_macros: bool, pub proc_macro_srv: Option, - pub skip_lowering: bool, pub skip_lang_items: bool, + pub skip_lowering: bool, pub skip_inference: bool, pub skip_mir_stats: bool, pub skip_data_layout: bool, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/parse.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/parse.rs index 85ec95409ae7..aa1b659d8bcd 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/parse.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/parse.rs @@ -1,18 +1,101 @@ //! Read Rust code on stdin, print syntax tree on stdout. use ide::Edition; -use syntax::{AstNode, SourceFile}; +use ide_db::line_index::LineIndex; +use serde::Serialize; +use syntax::{AstNode, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken}; use crate::cli::{flags, read_stdin}; +#[derive(Serialize)] +struct JsonNode { + kind: String, + #[serde(rename = "type")] + node_type: &'static str, + start: [u32; 3], + end: [u32; 3], + #[serde(skip_serializing_if = "Option::is_none")] + text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + children: Option>, +} + +fn pos(line_index: &LineIndex, offset: syntax::TextSize) -> [u32; 3] { + let offset_u32 = u32::from(offset); + let line_col = line_index.line_col(offset); + [offset_u32, line_col.line, line_col.col] +} + impl flags::Parse { pub fn run(self) -> anyhow::Result<()> { let _p = tracing::info_span!("flags::Parse::run").entered(); let text = read_stdin()?; + let line_index = LineIndex::new(&text); let file = SourceFile::parse(&text, Edition::CURRENT).tree(); + if !self.no_dump { - println!("{:#?}", file.syntax()); + if self.json { + let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index); + println!("{}", serde_json::to_string(&json_tree)?); + } else { + println!("{:#?}", file.syntax()); + } } + std::mem::forget(file); Ok(()) } } + +fn node_to_json(node: NodeOrToken, line_index: &LineIndex) -> JsonNode { + let range = node.text_range(); + let kind = format!("{:?}", node.kind()); + + match node { + NodeOrToken::Node(n) => { + let children: Vec<_> = + n.children_with_tokens().map(|it| node_to_json(it, line_index)).collect(); + JsonNode { + kind, + node_type: "Node", + start: pos(line_index, range.start()), + end: pos(line_index, range.end()), + text: None, + children: Some(children), + } + } + NodeOrToken::Token(t) => JsonNode { + kind, + node_type: "Token", + start: pos(line_index, range.start()), + end: pos(line_index, range.end()), + text: Some(t.text().to_owned()), + children: None, + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::flags; + + #[test] + fn test_parse_json_output() { + let text = "fn main() {}".to_owned(); + let flags = flags::Parse { json: true, no_dump: false }; + let line_index = LineIndex::new(&text); + + let file = SourceFile::parse(&text, Edition::CURRENT).tree(); + + let output = if flags.json { + let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index); + serde_json::to_string(&json_tree).unwrap() + } else { + format!("{:#?}", file.syntax()) + }; + + assert!(output.contains(r#""kind":"SOURCE_FILE""#)); + assert!(output.contains(r#""text":"main""#)); + assert!(output.contains(r#""start":[0,0,0]"#)); + } +} From b3d440ffdbe8673d3d11210038371123d8c64d80 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 8 Mar 2026 18:41:51 +0200 Subject: [PATCH 20/30] Allow duplicate assoc type shorthand resolution if it points to the same assoc type And with the same generic args. --- .../rust-analyzer/crates/hir-ty/src/lower.rs | 8 ++++-- .../crates/hir-ty/src/tests/regression.rs | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index c49e94343793..83b67bf1fe1f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -1809,8 +1809,12 @@ fn resolve_type_param_assoc_type_shorthand( return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: Some(this_trait_resolution), }; - } else if supertraits_resolution.is_some() { - return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None }; + } else if let Some(prev_resolution) = &supertraits_resolution { + if prev_resolution == lookup_on_bounded_trait { + return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None }; + } else { + continue; + } } else { let (assoc_type, args) = assoc_type_and_args .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index 1939db0ef5a7..d88801a57b86 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -2815,3 +2815,28 @@ fn contains_0>(points: &S) { "#, ); } + +#[test] +fn regression_21773() { + check_no_mismatches( + r#" +trait Neg { + type Output; +} + +trait Abs: Neg { + fn abs(&self) -> Self::Output; +} + +trait SelfAbs: Abs + Neg +where + Self::Output: Neg + Abs, +{ +} + +fn wrapped_abs>(v: T) -> T { + v.abs() +} + "#, + ); +} From c597a88a3813c3329d2cc89e3a1c706a8434c17b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 8 Mar 2026 18:56:36 +0100 Subject: [PATCH 21/30] Remove crate from salsa `EditionedFileId` --- .../crates/base-db/src/editioned_file_id.rs | 321 ++---------------- .../rust-analyzer/crates/base-db/src/input.rs | 2 +- .../rust-analyzer/crates/hir-def/src/db.rs | 2 +- .../src/expr_store/tests/body/block.rs | 2 +- .../crates/hir-def/src/item_tree.rs | 27 +- .../crates/hir-def/src/item_tree/lower.rs | 8 +- .../crates/hir-def/src/item_tree/tests.rs | 2 +- .../hir-def/src/macro_expansion_tests/mbe.rs | 16 +- .../hir-def/src/macro_expansion_tests/mod.rs | 2 +- .../crates/hir-def/src/nameres/collector.rs | 14 +- .../hir-def/src/nameres/mod_resolution.rs | 5 +- .../hir-def/src/nameres/tests/incremental.rs | 5 +- .../crates/hir-expand/src/builtin/fn_macro.rs | 2 +- .../rust-analyzer/crates/hir-expand/src/db.rs | 2 +- .../crates/hir-expand/src/lib.rs | 10 +- .../crates/hir-expand/src/span_map.rs | 2 +- .../crates/hir-ty/src/tests/incremental.rs | 7 + src/tools/rust-analyzer/crates/hir/src/lib.rs | 2 +- .../rust-analyzer/crates/hir/src/semantics.rs | 4 +- .../hir/src/semantics/child_by_source.rs | 3 +- .../rust-analyzer/crates/ide-db/src/search.rs | 12 +- .../ide-db/src/test_data/test_doc_alias.txt | 2 +- .../test_symbol_index_collection.txt | 10 +- .../test_symbols_exclude_imports.txt | 2 +- .../test_data/test_symbols_with_imports.txt | 4 +- .../rust-analyzer/crates/ide-db/src/traits.rs | 4 +- .../crates/ide-ssr/src/from_comment.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 13 +- .../crates/ide/src/references.rs | 5 +- .../crates/ide/src/signature_help.rs | 4 +- .../rust-analyzer/crates/ide/src/typing.rs | 6 +- .../crates/ide/src/typing/on_enter.rs | 2 +- .../crates/ide/src/view_item_tree.rs | 5 +- .../crates/load-cargo/src/lib.rs | 6 +- .../rust-analyzer/src/cli/analysis_stats.rs | 12 +- .../crates/rust-analyzer/src/cli/ssr.rs | 2 +- .../crates/test-fixture/src/lib.rs | 11 +- 37 files changed, 134 insertions(+), 406 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs index 062e3b59d941..db3730bccdf4 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs @@ -1,317 +1,46 @@ //! Defines [`EditionedFileId`], an interned wrapper around [`span::EditionedFileId`] that //! is interned (so queries can take it) and remembers its crate. -use core::fmt; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; +use salsa::Database; use span::Edition; use vfs::FileId; -use crate::{Crate, RootQueryDb}; - -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct EditionedFileId( - salsa::Id, - std::marker::PhantomData<&'static salsa::plumbing::interned::Value>, -); - -const _: () = { - use salsa::plumbing as zalsa_; - use zalsa_::interned as zalsa_struct_; - type Configuration_ = EditionedFileId; - - #[derive(Debug, Clone, Eq)] - pub struct EditionedFileIdData { - editioned_file_id: span::EditionedFileId, - krate: Crate, - } - - // FIXME: This poses an invalidation problem, if one constructs an `EditionedFileId` with a - // different crate then whatever the input of a memo used, it will invalidate the memo causing - // it to recompute even if the crate is not really used. - /// We like to include the origin crate in an `EditionedFileId` (for use in the item tree), - /// but this poses us a problem. - /// - /// Spans contain `EditionedFileId`s, and we don't want to make them store the crate too - /// because that will increase their size, which will increase memory usage significantly. - /// Furthermore, things using spans do not generally need the crate: they are using the - /// file id for queries like `ast_id_map` or `parse`, which do not care about the crate. - /// - /// To solve this, we hash **only the `span::EditionedFileId`**, but on still compare - /// the crate in equality check. This preserves the invariant of `Hash` and `Eq` - - /// although same hashes can be used for different items, same file ids used for multiple - /// crates is a rare thing, and different items always have different hashes. Then, - /// when we only have a `span::EditionedFileId`, we use the `intern()` method to - /// reuse existing file ids, and create new one only if needed. See [`from_span_guess_origin`]. - /// - /// See this for more info: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Letting.20EditionedFileId.20know.20its.20crate/near/530189401 - /// - /// [`from_span_guess_origin`]: EditionedFileId::from_span_guess_origin - #[derive(Hash, PartialEq, Eq)] - struct WithoutCrate { - editioned_file_id: span::EditionedFileId, - } - - impl PartialEq for EditionedFileIdData { - fn eq(&self, other: &Self) -> bool { - let Self { editioned_file_id, krate: _ } = self; - let Self { editioned_file_id: other_editioned_file_id, krate: _ } = other; - editioned_file_id == other_editioned_file_id - } - } - - impl Hash for EditionedFileIdData { - #[inline] - fn hash(&self, state: &mut H) { - let EditionedFileIdData { editioned_file_id, krate: _ } = *self; - editioned_file_id.hash(state); - } - } - - impl zalsa_struct_::HashEqLike for EditionedFileIdData { - #[inline] - fn hash(&self, state: &mut H) { - Hash::hash(self, state); - } - - #[inline] - fn eq(&self, data: &WithoutCrate) -> bool { - let EditionedFileIdData { editioned_file_id, krate: _ } = *self; - editioned_file_id == data.editioned_file_id - } - } - - impl zalsa_::HasJar for EditionedFileId { - type Jar = zalsa_struct_::JarImpl; - const KIND: zalsa_::JarKind = zalsa_::JarKind::Struct; - } - - zalsa_::register_jar! { - zalsa_::ErasedJar::erase::() - } - - impl zalsa_struct_::Configuration for EditionedFileId { - const LOCATION: salsa::plumbing::Location = - salsa::plumbing::Location { file: file!(), line: line!() }; - const DEBUG_NAME: &'static str = "EditionedFileId"; - const REVISIONS: std::num::NonZeroUsize = std::num::NonZeroUsize::MAX; - const PERSIST: bool = false; - - type Fields<'a> = EditionedFileIdData; - type Struct<'db> = EditionedFileId; - - fn serialize(_: &Self::Fields<'_>, _: S) -> Result - where - S: zalsa_::serde::Serializer, - { - unimplemented!("attempted to serialize value that set `PERSIST` to false") - } - - fn deserialize<'de, D>(_: D) -> Result, D::Error> - where - D: zalsa_::serde::Deserializer<'de>, - { - unimplemented!("attempted to deserialize value that cannot set `PERSIST` to false"); - } - } - - impl Configuration_ { - pub fn ingredient(zalsa: &zalsa_::Zalsa) -> &zalsa_struct_::IngredientImpl { - static CACHE: zalsa_::IngredientCache> = - zalsa_::IngredientCache::new(); - - // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only - // ingredient created by our jar is the struct ingredient. - unsafe { - CACHE.get_or_create(zalsa, || { - zalsa.lookup_jar_by_type::>() - }) - } - } - } - - impl zalsa_::AsId for EditionedFileId { - fn as_id(&self) -> salsa::Id { - self.0.as_id() - } - } - impl zalsa_::FromId for EditionedFileId { - fn from_id(id: salsa::Id) -> Self { - Self(::from_id(id), std::marker::PhantomData) - } - } - - unsafe impl Send for EditionedFileId {} - unsafe impl Sync for EditionedFileId {} - - impl std::fmt::Debug for EditionedFileId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Self::default_debug_fmt(*self, f) - } - } - - impl zalsa_::SalsaStructInDb for EditionedFileId { - type MemoIngredientMap = salsa::plumbing::MemoIngredientSingletonIndex; - - fn lookup_ingredient_index(aux: &zalsa_::Zalsa) -> salsa::plumbing::IngredientIndices { - aux.lookup_jar_by_type::>().into() - } - - fn entries(zalsa: &zalsa_::Zalsa) -> impl Iterator + '_ { - let _ingredient_index = - zalsa.lookup_jar_by_type::>(); - ::ingredient(zalsa).entries(zalsa).map(|entry| entry.key()) - } - - #[inline] - fn cast(id: salsa::Id, type_id: std::any::TypeId) -> Option { - if type_id == std::any::TypeId::of::() { - Some(::from_id(id)) - } else { - None - } - } - - #[inline] - unsafe fn memo_table( - zalsa: &zalsa_::Zalsa, - id: zalsa_::Id, - current_revision: zalsa_::Revision, - ) -> zalsa_::MemoTableWithTypes<'_> { - // SAFETY: Guaranteed by caller. - unsafe { - zalsa.table().memos::>(id, current_revision) - } - } - } - - unsafe impl zalsa_::Update for EditionedFileId { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - if unsafe { *old_pointer } != new_value { - unsafe { *old_pointer = new_value }; - true - } else { - false - } - } - } - - impl EditionedFileId { - pub fn from_span( - db: &(impl salsa::Database + ?Sized), - editioned_file_id: span::EditionedFileId, - krate: Crate, - ) -> Self { - let (zalsa, zalsa_local) = db.zalsas(); - Configuration_::ingredient(zalsa).intern( - zalsa, - zalsa_local, - EditionedFileIdData { editioned_file_id, krate }, - |_, data| data, - ) - } - - /// Guesses the crate for the file. - /// - /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: - /// - /// 1. The file is not in the module tree. - /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin - /// (e.g. on enter feature, folding, etc.). - // FIXME: Remove this and all the weird crate ignoring plumbing around this - // This can cause a variety of weird bugs https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Broken.20token.20mapping/with/577739887 - pub fn from_span_guess_origin( - db: &dyn RootQueryDb, - editioned_file_id: span::EditionedFileId, - ) -> Self { - let (zalsa, zalsa_local) = db.zalsas(); - Configuration_::ingredient(zalsa).intern( - zalsa, - zalsa_local, - WithoutCrate { editioned_file_id }, - |_, _| { - // FileId not in the database. - let krate = db - .relevant_crates(editioned_file_id.file_id()) - .first() - .copied() - .or_else(|| db.all_crates().first().copied()) - .unwrap_or_else(|| { - // What we're doing here is a bit fishy. We rely on the fact that we only need - // the crate in the item tree, and we should not create an `EditionedFileId` - // without a crate except in cases where it does not matter. The chances that - // `all_crates()` will be empty are also very slim, but it can occur during startup. - // In the very unlikely case that there is a bug and we'll use this crate, Salsa - // will panic. - - // SAFETY: 0 is less than `Id::MAX_U32`. - salsa::plumbing::FromId::from_id(unsafe { salsa::Id::from_index(0) }) - }); - EditionedFileIdData { editioned_file_id, krate } - }, - ) - } - - pub fn editioned_file_id(self, db: &dyn salsa::Database) -> span::EditionedFileId { - let zalsa = db.zalsa(); - let fields = Configuration_::ingredient(zalsa).fields(zalsa, self); - fields.editioned_file_id - } - - pub fn krate(self, db: &dyn salsa::Database) -> Crate { - let zalsa = db.zalsa(); - let fields = Configuration_::ingredient(zalsa).fields(zalsa, self); - fields.krate - } - - /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl) - pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - zalsa_::with_attached_database(|db| { - let zalsa = db.zalsa(); - let fields = Configuration_::ingredient(zalsa).fields(zalsa, this); - fmt::Debug::fmt(fields, f) - }) - .unwrap_or_else(|| { - f.debug_tuple("EditionedFileId").field(&zalsa_::AsId::as_id(&this)).finish() - }) - } - } -}; +#[salsa::interned(debug, constructor = from_span_file_id, no_lifetime)] +#[derive(PartialOrd, Ord)] +pub struct EditionedFileId { + field: span::EditionedFileId, +} impl EditionedFileId { #[inline] - pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition, krate: Crate) -> Self { - EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition), krate) - } - - /// Attaches the current edition and guesses the crate for the file. - /// - /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: - /// - /// 1. The file is not in the module tree. - /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin - /// (e.g. on enter feature, folding, etc.). - // FIXME: Remove this and all the weird crate ignoring plumbing around this - // This can cause a variety of weird bugs https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Broken.20token.20mapping/with/577739887 - #[inline] - pub fn current_edition_guess_origin(db: &dyn RootQueryDb, file_id: FileId) -> Self { - Self::from_span_guess_origin(db, span::EditionedFileId::current_edition(file_id)) + pub fn new(db: &dyn Database, file_id: FileId, edition: Edition) -> Self { + Self::from_span_file_id(db, span::EditionedFileId::new(file_id, edition)) } #[inline] - pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId { - let id = self.editioned_file_id(db); - id.file_id() + pub fn current_edition(db: &dyn Database, file_id: FileId) -> Self { + Self::from_span_file_id(db, span::EditionedFileId::current_edition(file_id)) } #[inline] - pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) { - let id = self.editioned_file_id(db); - (id.file_id(), id.edition()) + pub fn file_id(self, db: &dyn Database) -> vfs::FileId { + self.field(db).file_id() } #[inline] - pub fn edition(self, db: &dyn salsa::Database) -> Edition { - self.editioned_file_id(db).edition() + pub fn span_file_id(self, db: &dyn Database) -> span::EditionedFileId { + self.field(db) + } + + #[inline] + pub fn unpack(self, db: &dyn Database) -> (vfs::FileId, span::Edition) { + self.field(db).unpack() + } + + #[inline] + pub fn edition(self, db: &dyn Database) -> Edition { + self.field(db).edition() } } diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 151aba82a203..246c57edc2df 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -870,7 +870,7 @@ pub fn shrink_to_fit(&mut self) { impl Crate { pub fn root_file_id(self, db: &dyn salsa::Database) -> EditionedFileId { let data = self.data(db); - EditionedFileId::new(db, data.root_file_id, data.edition, self) + EditionedFileId::new(db, data.root_file_id, data.edition) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index ccd4bc9be84a..b0b652a1509c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -96,7 +96,7 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + SourceDatabase { /// Computes an [`ItemTree`] for the given file or macro expansion. #[salsa::invoke(file_item_tree_query)] #[salsa::transparent] - fn file_item_tree(&self, file_id: HirFileId) -> &ItemTree; + fn file_item_tree(&self, file_id: HirFileId, krate: Crate) -> &ItemTree; /// Turns a MacroId into a MacroDefId, describing the macro's definition post name resolution. #[salsa::invoke(macro_def)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs index d457a4ca7a3b..83594ee02169 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs @@ -196,7 +196,7 @@ fn f() { ), block: Some( BlockId( - 4401, + 4801, ), ), }"#]], diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index a1707f17beb0..9825dbfe1cd2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -44,6 +44,7 @@ }; use ast::{AstNode, StructKind}; +use base_db::Crate; use cfg::CfgOptions; use hir_expand::{ ExpandTo, HirFileId, @@ -121,21 +122,23 @@ fn span_for(&self, range: TextRange) -> Span { } #[salsa_macros::tracked(returns(deref))] -pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc { +pub(crate) fn file_item_tree_query( + db: &dyn DefDatabase, + file_id: HirFileId, + krate: Crate, +) -> Arc { let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered(); static EMPTY: OnceLock> = OnceLock::new(); - let ctx = lower::Ctx::new(db, file_id); + let ctx = lower::Ctx::new(db, file_id, krate); let syntax = db.parse_or_expand(file_id); let mut item_tree = match_ast! { match syntax { ast::SourceFile(file) => { - let krate = file_id.krate(db); let root_file_id = krate.root_file_id(db); let extra_top_attrs = (file_id == root_file_id).then(|| { parse_extra_crate_attrs(db, krate).map(|crate_attrs| { - let file_id = root_file_id.editioned_file_id(db); - lower_extra_crate_attrs(db, crate_attrs, file_id, &|| ctx.cfg_options()) + lower_extra_crate_attrs(db, crate_attrs, root_file_id.span_file_id(db), &|| ctx.cfg_options()) }) }).flatten(); let top_attrs = match extra_top_attrs { @@ -190,14 +193,18 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> } #[salsa_macros::tracked(returns(deref))] -pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc { +pub(crate) fn block_item_tree_query( + db: &dyn DefDatabase, + block: BlockId, + krate: Crate, +) -> Arc { let _p = tracing::info_span!("block_item_tree_query", ?block).entered(); static EMPTY: OnceLock> = OnceLock::new(); let loc = block.lookup(db); let block = loc.ast_id.to_node(db); - let ctx = lower::Ctx::new(db, loc.ast_id.file_id); + let ctx = lower::Ctx::new(db, loc.ast_id.file_id, krate); let mut item_tree = ctx.lower_block(&block); let ItemTree { top_level, top_attrs, attrs, vis, big_data, small_data } = &item_tree; if small_data.is_empty() @@ -356,10 +363,10 @@ pub(crate) fn new(file: HirFileId, block: Option) -> Self { Self { file, block } } - pub(crate) fn item_tree<'db>(&self, db: &'db dyn DefDatabase) -> &'db ItemTree { + pub(crate) fn item_tree<'db>(&self, db: &'db dyn DefDatabase, krate: Crate) -> &'db ItemTree { match self.block { - Some(block) => block_item_tree_query(db, block), - None => file_item_tree_query(db, self.file), + Some(block) => block_item_tree_query(db, block, krate), + None => file_item_tree_query(db, self.file, krate), } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index 3f19e001548e..31c6ef867d2c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -2,7 +2,7 @@ use std::cell::OnceCell; -use base_db::FxIndexSet; +use base_db::{Crate, FxIndexSet}; use cfg::CfgOptions; use hir_expand::{ HirFileId, @@ -36,12 +36,13 @@ pub(super) struct Ctx<'a> { span_map: OnceCell, file: HirFileId, cfg_options: OnceCell<&'a CfgOptions>, + krate: Crate, top_level: Vec, visibilities: FxIndexSet, } impl<'a> Ctx<'a> { - pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self { + pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId, krate: Crate) -> Self { Self { db, tree: ItemTree::default(), @@ -51,12 +52,13 @@ pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self { span_map: OnceCell::new(), visibilities: FxIndexSet::default(), top_level: Vec::new(), + krate, } } #[inline] pub(super) fn cfg_options(&self) -> &'a CfgOptions { - self.cfg_options.get_or_init(|| self.file.krate(self.db).cfg_options(self.db)) + self.cfg_options.get_or_init(|| self.krate.cfg_options(self.db)) } pub(super) fn span_map(&self) -> SpanMapRef<'_> { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs index 1926ed74e869..b71b25a1a51d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs @@ -6,7 +6,7 @@ fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { let (db, file_id) = TestDB::with_single_file(ra_fixture); - let item_tree = db.file_item_tree(file_id.into()); + let item_tree = db.file_item_tree(file_id.into(), db.test_crate()); let pretty = item_tree.pretty_print(&db, Edition::CURRENT); expect.assert_eq(&pretty); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index 7b5d0103e66e..d93df7af6a73 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -35,9 +35,9 @@ struct $ident { }; } -struct#0:MacroRules[BE8F, 0]@58..64#17408# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#17408# - map#0:MacroRules[BE8F, 0]@86..89#17408#:#0:MacroRules[BE8F, 0]@89..90#17408# #0:MacroRules[BE8F, 0]@89..90#17408#::#0:MacroRules[BE8F, 0]@91..93#17408#std#0:MacroRules[BE8F, 0]@93..96#17408#::#0:MacroRules[BE8F, 0]@96..98#17408#collections#0:MacroRules[BE8F, 0]@98..109#17408#::#0:MacroRules[BE8F, 0]@109..111#17408#HashSet#0:MacroRules[BE8F, 0]@111..118#17408#<#0:MacroRules[BE8F, 0]@118..119#17408#(#0:MacroRules[BE8F, 0]@119..120#17408#)#0:MacroRules[BE8F, 0]@120..121#17408#>#0:MacroRules[BE8F, 0]@121..122#17408#,#0:MacroRules[BE8F, 0]@122..123#17408# -}#0:MacroRules[BE8F, 0]@132..133#17408# +struct#0:MacroRules[BE8F, 0]@58..64#18432# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#18432# + map#0:MacroRules[BE8F, 0]@86..89#18432#:#0:MacroRules[BE8F, 0]@89..90#18432# #0:MacroRules[BE8F, 0]@89..90#18432#::#0:MacroRules[BE8F, 0]@91..93#18432#std#0:MacroRules[BE8F, 0]@93..96#18432#::#0:MacroRules[BE8F, 0]@96..98#18432#collections#0:MacroRules[BE8F, 0]@98..109#18432#::#0:MacroRules[BE8F, 0]@109..111#18432#HashSet#0:MacroRules[BE8F, 0]@111..118#18432#<#0:MacroRules[BE8F, 0]@118..119#18432#(#0:MacroRules[BE8F, 0]@119..120#18432#)#0:MacroRules[BE8F, 0]@120..121#18432#>#0:MacroRules[BE8F, 0]@121..122#18432#,#0:MacroRules[BE8F, 0]@122..123#18432# +}#0:MacroRules[BE8F, 0]@132..133#18432# "#]], ); } @@ -197,7 +197,7 @@ macro_rules! mk_struct { #[macro_use] mod foo; -struct#1:MacroRules[DB0C, 0]@59..65#17408# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#17408#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#17408#;#1:MacroRules[DB0C, 0]@75..76#17408# +struct#1:MacroRules[DB0C, 0]@59..65#18432# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#18432#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#18432#;#1:MacroRules[DB0C, 0]@75..76#18432# "#]], ); } @@ -423,10 +423,10 @@ macro_rules! m { macro_rules! m { ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); } -impl#\17408# Bar#\17408# {#\17408# - fn#\17408# foo#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# - fn#\17408# bar#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# -}#\17408# +impl#\18432# Bar#\18432# {#\18432# + fn#\18432# foo#\ROOT2024#(#\18432#)#\18432# {#\18432#}#\18432# + fn#\18432# bar#\ROOT2024#(#\18432#)#\18432# {#\18432#}#\18432# +}#\18432# "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index c63f2c1d786b..8ee93dcaa32d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -458,7 +458,7 @@ fn $func_name() { todo!() } "#; let (db, file_id) = TestDB::with_single_file(fixture); - let krate = file_id.krate(&db); + let krate = db.test_crate(); let def_map = crate_def_map(&db, krate); let source = def_map[def_map.root].definition_source(&db); let source_file = match source.value { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index e672e83f0194..9c101c127b41 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -279,7 +279,7 @@ fn seed_with_top_level(&mut self) { let _p = tracing::info_span!("seed_with_top_level").entered(); let file_id = self.def_map.krate.root_file_id(self.db); - let item_tree = self.db.file_item_tree(file_id.into()); + let item_tree = self.db.file_item_tree(file_id.into(), self.def_map.krate); let attrs = match item_tree.top_level_attrs() { AttrsOrCfg::Enabled { attrs } => attrs.as_ref(), AttrsOrCfg::CfgDisabled(it) => it.1.as_ref(), @@ -387,7 +387,7 @@ fn seed_with_top_level(&mut self) { } fn seed_with_inner(&mut self, tree_id: TreeId) { - let item_tree = tree_id.item_tree(self.db); + let item_tree = tree_id.item_tree(self.db, self.def_map.krate); let is_cfg_enabled = matches!(item_tree.top_level_attrs(), AttrsOrCfg::Enabled { .. }); if is_cfg_enabled { self.inject_prelude(); @@ -1708,7 +1708,7 @@ fn collect_macro_expansion( } let file_id = macro_call_id.into(); - let item_tree = self.db.file_item_tree(file_id); + let item_tree = self.db.file_item_tree(file_id, self.def_map.krate); // Derive helpers that are in scope for an item are also in scope for attribute macro expansions // of that item (but not derive or fn like macros). @@ -2335,10 +2335,10 @@ fn collect_module(&mut self, module_ast_id: ItemTreeAstId, attrs: Attrs<'_> self.file_id(), &module.name, path_attr.as_deref(), - self.def_collector.def_map.krate, ) { Ok((file_id, is_mod_rs, mod_dir)) => { - let item_tree = db.file_item_tree(file_id.into()); + let item_tree = + db.file_item_tree(file_id.into(), self.def_collector.def_map.krate); match item_tree.top_level_attrs() { AttrsOrCfg::CfgDisabled(cfg) => { self.emit_unconfigured_diagnostic( @@ -2828,8 +2828,8 @@ fn crate_attrs() { let fixture = r#" //- /lib.rs crate:foo crate-attr:recursion_limit="4" crate-attr:no_core crate-attr:no_std crate-attr:feature(register_tool) "#; - let (db, file_id) = TestDB::with_single_file(fixture); - let def_map = crate_def_map(&db, file_id.krate(&db)); + let (db, _) = TestDB::with_single_file(fixture); + let def_map = crate_def_map(&db, db.test_crate()); assert_eq!(def_map.recursion_limit(), 4); assert!(def_map.is_no_core()); assert!(def_map.is_no_std()); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs index 140b77ac002f..0c50f13edfb6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs @@ -1,6 +1,6 @@ //! This module resolves `mod foo;` declaration to file. use arrayvec::ArrayVec; -use base_db::{AnchoredPath, Crate}; +use base_db::AnchoredPath; use hir_expand::{EditionedFileId, name::Name}; use crate::{HirFileId, db::DefDatabase}; @@ -62,7 +62,6 @@ pub(super) fn resolve_declaration( file_id: HirFileId, name: &Name, attr_path: Option<&str>, - krate: Crate, ) -> Result<(EditionedFileId, bool, ModDir), Box<[String]>> { let name = name.as_str(); @@ -92,7 +91,7 @@ pub(super) fn resolve_declaration( if let Some(mod_dir) = self.child(dir_path, !root_dir_owner) { return Ok(( // FIXME: Edition, is this rightr? - EditionedFileId::new(db, file_id, orig_file_id.edition(db), krate), + EditionedFileId::new(db, file_id, orig_file_id.edition(db)), is_mod_rs, mod_dir, )); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index b2a056ff2a60..7fedfa03bbd9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -118,6 +118,7 @@ pub fn foo() {} expect![[r#" [ "crate_local_def_map", + "file_item_tree_query", "crate_local_def_map", ] "#]], @@ -603,7 +604,7 @@ impl Tr for () {} execute_assert_events( &db, || { - db.file_item_tree(pos.file_id.into()); + db.file_item_tree(pos.file_id.into(), db.test_crate()); }, &[("file_item_tree_query", 1), ("parse", 1)], expect![[r#" @@ -623,7 +624,7 @@ impl Tr for () {} execute_assert_events( &db, || { - db.file_item_tree(pos.file_id.into()); + db.file_item_tree(pos.file_id.into(), db.test_crate()); }, &[("file_item_tree_query", 1), ("parse", 1)], expect![[r#" diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs index 2de7290a2198..b3572a1cefcc 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -774,7 +774,7 @@ fn relative_file( if res == call_site && !allow_recursion { Err(ExpandError::other(err_span, format!("recursive inclusion of `{path_str}`"))) } else { - Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition, lookup.krate)) + Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition)) } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 51767f87ffb9..363465fdda35 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -162,7 +162,7 @@ fn syntax_context(db: &dyn ExpandDatabase, file: HirFileId, edition: Edition) -> } fn resolve_span(db: &dyn ExpandDatabase, Span { range, anchor, ctx: _ }: Span) -> FileRange { - let file_id = EditionedFileId::from_span_guess_origin(db, anchor.file_id); + let file_id = EditionedFileId::from_span_file_id(db, anchor.file_id); let anchor_offset = db.ast_id_map(file_id.into()).get_erased(anchor.ast_id).text_range().start(); FileRange { file_id, range: range + anchor_offset } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 05541e782efd..4b2c75ed386e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -386,7 +386,7 @@ pub(crate) fn call_style(&self) -> MacroCallStyle { impl HirFileId { pub fn edition(self, db: &dyn ExpandDatabase) -> Edition { match self { - HirFileId::FileId(file_id) => file_id.editioned_file_id(db).edition(), + HirFileId::FileId(file_id) => file_id.edition(db), HirFileId::MacroFile(m) => db.lookup_intern_macro_call(m).def.edition, } } @@ -1118,14 +1118,6 @@ pub fn file_id(self) -> Option { HirFileId::MacroFile(_) => None, } } - - #[inline] - pub fn krate(self, db: &dyn ExpandDatabase) -> Crate { - match self { - HirFileId::FileId(it) => it.krate(db), - HirFileId::MacroFile(it) => it.loc(db).krate, - } - } } impl PartialEq for HirFileId { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs index 586b8152947b..71d0b880caa1 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs @@ -135,7 +135,7 @@ pub(crate) fn real_span_map( }); Arc::new(RealSpanMap::from_file( - editioned_file_id.editioned_file_id(db), + editioned_file_id.span_file_id(db), pairs.into_boxed_slice(), tree.syntax().text_range().end(), )) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index cf7ff6f7ecca..faa7b80a89c2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -31,6 +31,7 @@ fn foo() -> i32 { &[("InferenceResult::for_body_", 1)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -118,6 +119,7 @@ fn baz() -> i32 { &[("InferenceResult::for_body_", 3)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -237,6 +239,7 @@ fn bar() -> f32 { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -311,6 +314,7 @@ fn bar() -> f32 { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -386,6 +390,7 @@ fn bar() -> f32 { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -462,6 +467,7 @@ pub struct SomeStruct { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -562,6 +568,7 @@ fn main() { &[("trait_solve_shim", 0)], expect_test::expect![[r#" [ + "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 11d79e2d7bf8..0b3515fd0498 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -1091,7 +1091,7 @@ fn macro_call_diagnostics<'db>( let file_id = loc.kind.file_id(); let mut range = precise_macro_call_location(&loc.kind, db, loc.krate); let RenderedExpandError { message, error, kind } = err.render_to_string(db); - if Some(err.span().anchor.file_id) == file_id.file_id().map(|it| it.editioned_file_id(db)) { + if Some(err.span().anchor.file_id) == file_id.file_id().map(|it| it.span_file_id(db)) { range.value = err.span().range + db.ast_id_map(file_id).get_erased(err.span().anchor.ast_id).text_range().start(); } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 1cf3b9816082..c816fe967c54 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -472,12 +472,12 @@ pub fn first_crate(&self, file: FileId) -> Option { pub fn attach_first_edition_opt(&self, file: FileId) -> Option { let krate = self.file_to_module_defs(file).next()?.krate(self.db); - Some(EditionedFileId::new(self.db, file, krate.edition(self.db), krate.id)) + Some(EditionedFileId::new(self.db, file, krate.edition(self.db))) } pub fn attach_first_edition(&self, file: FileId) -> EditionedFileId { self.attach_first_edition_opt(file) - .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(self.db, file)) + .unwrap_or_else(|| EditionedFileId::current_edition(self.db, file)) } pub fn parse_guess_edition(&self, file_id: FileId) -> ast::SourceFile { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs index c1f72debe54f..143cc14c3377 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs @@ -93,7 +93,6 @@ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: Hi impl ChildBySource for ItemScope { fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { - let krate = file_id.krate(db); self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); self.impls().for_each(|imp| insert_item_loc(db, res, file_id, imp, keys::IMPL)); self.extern_blocks().for_each(|extern_block| { @@ -123,6 +122,8 @@ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: Hi |(ast_id, calls)| { let adt = ast_id.to_node(db); calls.for_each(|(attr_id, call_id, calls)| { + // FIXME: Is this the right crate? + let krate = call_id.lookup(db).krate; // FIXME: Fix cfg_attr handling. let (attr, _, _, _) = attr_id.find_attr_range_with_source(db, krate, &adt); res[keys::DERIVE_MACRO_CALL] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index 4196a13aa3fa..3822eaae2cc7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -169,7 +169,7 @@ fn crate_graph(db: &RootDatabase) -> SearchScope { entries.extend( source_root .iter() - .map(|id| (EditionedFileId::new(db, id, crate_data.edition, krate), None)), + .map(|id| (EditionedFileId::new(db, id, crate_data.edition), None)), ); } SearchScope { entries } @@ -183,9 +183,11 @@ fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope { let source_root = db.file_source_root(root_file).source_root_id(db); let source_root = db.source_root(source_root).source_root(db); - entries.extend(source_root.iter().map(|id| { - (EditionedFileId::new(db, id, rev_dep.edition(db), rev_dep.into()), None) - })); + entries.extend( + source_root + .iter() + .map(|id| (EditionedFileId::new(db, id, rev_dep.edition(db)), None)), + ); } SearchScope { entries } } @@ -199,7 +201,7 @@ fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope { SearchScope { entries: source_root .iter() - .map(|id| (EditionedFileId::new(db, id, of.edition(db), of.into()), None)) + .map(|id| (EditionedFileId::new(db, id, of.edition(db)), None)) .collect(), } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt index 0c28c312f83b..fc98ebb06921 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt @@ -2,7 +2,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3800), + [salsa id]: Id(3400), }, }, [ diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 4b588572d328..46d938b5a503 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -2,7 +2,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3800), + [salsa id]: Id(3400), }, }, [ @@ -671,7 +671,7 @@ def: Module( Module { id: ModuleIdLt { - [salsa id]: Id(3801), + [salsa id]: Id(3401), }, }, ), @@ -706,7 +706,7 @@ def: Module( Module { id: ModuleIdLt { - [salsa id]: Id(3802), + [salsa id]: Id(3402), }, }, ), @@ -998,7 +998,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3801), + [salsa id]: Id(3401), }, }, [ @@ -1044,7 +1044,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3802), + [salsa id]: Id(3402), }, }, [ diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt index 87f0c7d9a817..aff1d56c56a3 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt @@ -5,7 +5,7 @@ Struct( Struct { id: StructId( - 3c00, + 4000, ), }, ), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt index e96aa889ba06..bf5d81cfb149 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt @@ -5,7 +5,7 @@ Struct( Struct { id: StructId( - 3c00, + 4000, ), }, ), @@ -42,7 +42,7 @@ Struct( Struct { id: StructId( - 3c00, + 4000, ), }, ), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/traits.rs b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs index 41ef50165383..60bdc2d82c16 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/traits.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs @@ -113,7 +113,6 @@ fn assoc_item_of_trait( #[cfg(test)] mod tests { - use base_db::RootQueryDb; use expect_test::{Expect, expect}; use hir::{EditionedFileId, FilePosition, Semantics}; use span::Edition; @@ -132,8 +131,7 @@ pub(crate) fn position( let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); - let &krate = database.relevant_crates(file_id.file_id()).first().unwrap(); - let file_id = EditionedFileId::from_span(&database, file_id, krate); + let file_id = EditionedFileId::from_span_file_id(&database, file_id); let offset = range_or_offset.expect_offset(); (database, FilePosition { file_id, offset }) } diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs index de26879c2959..181cc74a51d4 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs @@ -17,7 +17,7 @@ pub fn ssr_from_comment( frange: FileRange, ) -> Option<(MatchFinder<'_>, TextRange)> { let comment = { - let file_id = EditionedFileId::current_edition_guess_origin(db, frange.file_id); + let file_id = EditionedFileId::current_edition(db, frange.file_id); let file = db.parse(file_id); file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast) diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 930eaf2262d9..81a771fec89e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -339,8 +339,7 @@ pub fn file_text(&self, file_id: FileId) -> Cancellable> { pub fn parse(&self, file_id: FileId) -> Cancellable { // FIXME edition self.with_db(|db| { - let editioned_file_id_wrapper = - EditionedFileId::current_edition_guess_origin(&self.db, file_id); + let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); db.parse(editioned_file_id_wrapper).tree() }) @@ -369,7 +368,7 @@ pub fn extend_selection(&self, frange: FileRange) -> Cancellable { /// supported). pub fn matching_brace(&self, position: FilePosition) -> Cancellable> { self.with_db(|db| { - let file_id = EditionedFileId::current_edition_guess_origin(&self.db, position.file_id); + let file_id = EditionedFileId::current_edition(&self.db, position.file_id); let parse = db.parse(file_id); let file = parse.tree(); matching_brace::matching_brace(&file, position.offset) @@ -430,7 +429,7 @@ pub fn expand_macro(&self, position: FilePosition) -> Cancellable Cancellable { self.with_db(|db| { let editioned_file_id_wrapper = - EditionedFileId::current_edition_guess_origin(&self.db, frange.file_id); + EditionedFileId::current_edition(&self.db, frange.file_id); let parse = db.parse(editioned_file_id_wrapper); join_lines::join_lines(config, &parse.tree(), frange.range) }) @@ -471,8 +470,7 @@ pub fn file_structure( ) -> Cancellable> { // FIXME: Edition self.with_db(|db| { - let editioned_file_id_wrapper = - EditionedFileId::current_edition_guess_origin(&self.db, file_id); + let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); let source_file = db.parse(editioned_file_id_wrapper).tree(); file_structure::file_structure(&source_file, config) }) @@ -503,8 +501,7 @@ pub fn inlay_hints_resolve( /// Returns the set of folding ranges. pub fn folding_ranges(&self, file_id: FileId) -> Cancellable> { self.with_db(|db| { - let editioned_file_id_wrapper = - EditionedFileId::current_edition_guess_origin(&self.db, file_id); + let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); folding_ranges::folding_ranges(&db.parse(editioned_file_id_wrapper).tree()) }) diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 102eb91b74f7..6464c477160b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -1151,10 +1151,7 @@ pub fn quux$0() {} check_with_scope( code, Some(&mut |db| { - SearchScope::single_file(EditionedFileId::current_edition_guess_origin( - db, - FileId::from_raw(2), - )) + SearchScope::single_file(EditionedFileId::current_edition(db, FileId::from_raw(2))) }), expect![[r#" quux Function FileId(0) 19..35 26..30 diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index 9ab07565e9ef..f86974b4ec76 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -1975,8 +1975,8 @@ trait Sub: Super + Super { fn f() -> impl Sub<$0 "#, expect![[r#" - trait Sub - ^^^^^^^^^ ----------- + trait Sub + ^^^^^^^^^^^ --------- "#]], ); } diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index ca2194fceb67..11bfb982cf41 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -76,11 +76,7 @@ pub(crate) fn on_char_typed( .copied() .unwrap_or_else(|| *db.all_crates().first().unwrap()); let edition = krate.data(db).edition; - let editioned_file_id_wrapper = EditionedFileId::from_span( - db, - span::EditionedFileId::new(position.file_id, edition), - krate, - ); + let editioned_file_id_wrapper = EditionedFileId::new(db, position.file_id, edition); let file = &db.parse(editioned_file_id_wrapper); let char_matches_position = file.tree().syntax().text().char_at(position.offset) == Some(char_typed); diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs index 76a2802d294c..fdc583a15cc7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs @@ -51,7 +51,7 @@ // ![On Enter](https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif) pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { let editioned_file_id_wrapper = - ide_db::base_db::EditionedFileId::current_edition_guess_origin(db, position.file_id); + ide_db::base_db::EditionedFileId::current_edition(db, position.file_id); let parse = db.parse(editioned_file_id_wrapper); let file = parse.tree(); let token = file.syntax().token_at_offset(position.offset).left_biased()?; diff --git a/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs b/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs index e1a7e4e6ab23..8d84eba7ab65 100644 --- a/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs +++ b/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs @@ -10,6 +10,9 @@ // | VS Code | **rust-analyzer: Debug ItemTree** | pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String { let sema = Semantics::new(db); + let Some(krate) = sema.first_crate(file_id) else { + return String::new(); + }; let file_id = sema.attach_first_edition(file_id); - db.file_item_tree(file_id.into()).pretty_print(db, file_id.edition(db)) + db.file_item_tree(file_id.into(), krate.into()).pretty_print(db, file_id.edition(db)) } diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index b8ce3a8da4a2..95fcfce29127 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -638,7 +638,7 @@ fn expand( current_span = Span { range: resolved.range, anchor: SpanAnchor { - file_id: resolved.file_id.editioned_file_id(db), + file_id: resolved.file_id.span_file_id(db), ast_id: span::ROOT_ERASED_FILE_AST_ID, }, ctx: current_ctx, @@ -652,7 +652,7 @@ fn expand( let resolved = db.resolve_span(current_span); Ok(SubResponse::SpanSourceResult { - file_id: resolved.file_id.editioned_file_id(db).as_u32(), + file_id: resolved.file_id.span_file_id(db).as_u32(), ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(), start: u32::from(resolved.range.start()), end: u32::from(resolved.range.end()), @@ -684,7 +684,7 @@ fn expand( .text_range(); let parent_span = Some(ParentSpan { - file_id: editioned_file_id.editioned_file_id(db).as_u32(), + file_id: editioned_file_id.span_file_id(db).as_u32(), ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(), start: u32::from(range.start()), end: u32::from(range.end()), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 1995d3889891..caa35879aa00 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -126,7 +126,7 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> { let source_roots = krates .iter() .cloned() - .map(|krate| db.file_source_root(krate.root_file(db)).source_root_id(db)) + .map(|krate| (db.file_source_root(krate.root_file(db)).source_root_id(db), krate)) .unique(); let mut dep_loc = 0; @@ -137,7 +137,7 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> { let mut workspace_item_stats = PrettyItemStats::default(); let mut dep_item_stats = PrettyItemStats::default(); - for source_root_id in source_roots { + for (source_root_id, krate) in source_roots { let source_root = db.source_root(source_root_id).source_root(db); for file_id in source_root.iter() { if let Some(p) = source_root.path_for_file(&file_id) @@ -148,7 +148,8 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> { let length = db.file_text(file_id).text(db).lines().count(); let item_stats = db .file_item_tree( - EditionedFileId::current_edition_guess_origin(db, file_id).into(), + EditionedFileId::current_edition(db, file_id).into(), + krate.into(), ) .item_tree_stats() .into(); @@ -160,7 +161,8 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> { let length = db.file_text(file_id).text(db).lines().count(); let item_stats = db .file_item_tree( - EditionedFileId::current_edition_guess_origin(db, file_id).into(), + EditionedFileId::current_edition(db, file_id).into(), + krate.into(), ) .item_tree_stats() .into(); @@ -492,7 +494,7 @@ struct Acc { let mut sw = self.stop_watch(); for &file_id in file_ids { - let file_id = file_id.editioned_file_id(db); + let file_id = file_id.span_file_id(db); let sema = hir::Semantics::new(db); let display_target = match sema.first_crate(file_id.file_id()) { Some(krate) => krate.to_display_target(sema.db), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 5c69bda723fb..6bc0792daabb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -74,7 +74,7 @@ pub fn run(self) -> anyhow::Result<()> { let sr = db.source_root(root).source_root(db); for file_id in sr.iter() { for debug_info in match_finder.debug_where_text_equal( - EditionedFileId::current_edition_guess_origin(db, file_id), + EditionedFileId::current_edition(db, file_id), debug_snippet, ) { println!("{debug_info:#?}"); diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 93d4650cd3c4..e271c32c8626 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -149,8 +149,7 @@ fn with_single_file( let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture"); - let &krate = db.relevant_crates(fixture.files[0].file_id()).first().unwrap(); - let file_id = EditionedFileId::from_span(&db, fixture.files[0], krate); + let file_id = EditionedFileId::from_span_file_id(&db, fixture.files[0]); (db, file_id) } @@ -166,10 +165,7 @@ fn with_many_files( let files = fixture .files .into_iter() - .map(|file| { - let &krate = db.relevant_crates(file.file_id()).first().unwrap(); - EditionedFileId::from_span(&db, file, krate) - }) + .map(|file| EditionedFileId::from_span_file_id(&db, file)) .collect(); (db, files) } @@ -226,8 +222,7 @@ fn with_range_or_offset( let (file_id, range_or_offset) = fixture .file_position .expect("Could not find file position in fixture. Did you forget to add an `$0`?"); - let &krate = db.relevant_crates(file_id.file_id()).first().unwrap(); - let file_id = EditionedFileId::from_span(&db, file_id, krate); + let file_id = EditionedFileId::from_span_file_id(&db, file_id); (db, file_id, range_or_offset) } From 091f319bcd2d6ce40fbb4ee28e7ffe88d639c061 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 9 Mar 2026 04:43:44 +0000 Subject: [PATCH 22/30] Prepare for merging from rust-lang/rust This updates the rust-version file to eda4fc7733ee89e484d7120cafbd80dcb2fce66e. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 7c89bcb9ab04..db9492636f6a 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -f8704be04fe1150527fc2cf21dd44327f0fe87fb +eda4fc7733ee89e484d7120cafbd80dcb2fce66e From 167567407260e4c211374eb3dd25eef0157220c5 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 9 Mar 2026 11:08:57 +0100 Subject: [PATCH 23/30] Do not re-query source roots per crate in analysis-stats --- .../rust-analyzer/crates/base-db/src/editioned_file_id.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/typing.rs | 5 ++--- .../crates/rust-analyzer/src/cli/analysis_stats.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs index db3730bccdf4..8721f3a0ff3b 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs @@ -1,5 +1,5 @@ //! Defines [`EditionedFileId`], an interned wrapper around [`span::EditionedFileId`] that -//! is interned (so queries can take it) and remembers its crate. +//! is interned (so queries can take it) and stores only the underlying `span::EditionedFileId`. use std::hash::Hash; diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index 11bfb982cf41..e8b0c92dcb20 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -70,12 +70,11 @@ pub(crate) fn on_char_typed( if !TRIGGER_CHARS.contains(&char_typed) { return None; } - let krate = db + let edition = db .relevant_crates(position.file_id) .first() .copied() - .unwrap_or_else(|| *db.all_crates().first().unwrap()); - let edition = krate.data(db).edition; + .map_or(Edition::CURRENT, |krate| krate.data(db).edition); let editioned_file_id_wrapper = EditionedFileId::new(db, position.file_id, edition); let file = &db.parse(editioned_file_id_wrapper); let char_matches_position = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index caa35879aa00..82f04aa78ebb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -127,7 +127,7 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> { .iter() .cloned() .map(|krate| (db.file_source_root(krate.root_file(db)).source_root_id(db), krate)) - .unique(); + .unique_by(|(source_root_id, _)| *source_root_id); let mut dep_loc = 0; let mut workspace_loc = 0; From e0829846cede092c09ddad33710cfedfaaab81a3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 9 Mar 2026 12:28:02 +0200 Subject: [PATCH 24/30] Fix a stupid typo --- .../rust-analyzer/crates/hir-ty/src/lower.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 83b67bf1fe1f..49594f34fd73 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -1805,22 +1805,27 @@ fn resolve_type_param_assoc_type_shorthand( } AssocTypeShorthandResolution::Cycle => return AssocTypeShorthandResolution::Cycle, }; + let (assoc_type, args) = assoc_type_and_args + .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) + .skip_binder(); + let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args); + let current_result = StoredEarlyBinder::bind((assoc_type, args.store())); if let Some(this_trait_resolution) = this_trait_resolution { return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: Some(this_trait_resolution), }; } else if let Some(prev_resolution) = &supertraits_resolution { - if prev_resolution == lookup_on_bounded_trait { - return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None }; - } else { + if let AssocTypeShorthandResolution::Ambiguous { + sub_trait_resolution: Some(prev_resolution), + } + | AssocTypeShorthandResolution::Resolved(prev_resolution) = prev_resolution + && *prev_resolution == current_result + { continue; + } else { + return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None }; } } else { - let (assoc_type, args) = assoc_type_and_args - .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) - .skip_binder(); - let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args); - let current_result = StoredEarlyBinder::bind((assoc_type, args.store())); supertraits_resolution = Some(match lookup_on_bounded_trait { AssocTypeShorthandResolution::Resolved(_) => { AssocTypeShorthandResolution::Resolved(current_result) From cd09c1a326a6989c8b6e1fbefbe62510365b88d7 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 10 Mar 2026 21:46:14 +0800 Subject: [PATCH 25/30] fix: handle multi-byte UTF-8 identifiers in `NameGenerator::suggest_name` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `split_numeric_suffix` used `rfind` to locate the last non-numeric character and then split at `pos + 1`. Since `rfind` returns a byte offset, this panics when the last non-numeric character is multi-byte (e.g. CJK identifiers like `日本語`). Use `str::ceil_char_boundary` to advance past the full character before splitting. --- .../ide-db/src/syntax_helpers/suggest_name.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs index 5d1e876ea298..3a785fbe80a0 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs @@ -89,6 +89,12 @@ /// /// assert_eq!(generator.suggest_name("b2"), "b2"); /// assert_eq!(generator.suggest_name("b"), "b3"); +/// +/// // Multi-byte UTF-8 identifiers (e.g. CJK) are handled correctly +/// assert_eq!(generator.suggest_name("日本語"), "日本語"); +/// assert_eq!(generator.suggest_name("日本語"), "日本語1"); +/// assert_eq!(generator.suggest_name("données3"), "données3"); +/// assert_eq!(generator.suggest_name("données"), "données4"); /// ``` #[derive(Debug, Default)] pub struct NameGenerator { @@ -262,11 +268,15 @@ fn insert(&mut self, name: &str) { /// Remove the numeric suffix from the name /// /// # Examples - /// `a1b2c3` -> `a1b2c` + /// `a1b2c3` -> (`a1b2c`, Some(3)) fn split_numeric_suffix(name: &str) -> (&str, Option) { let pos = name.rfind(|c: char| !c.is_numeric()).expect("Name cannot be empty or all-numeric"); - let (prefix, suffix) = name.split_at(pos + 1); + // `rfind` returns the byte offset of the matched character, which may be + // multi-byte (e.g. CJK identifiers). Use `ceil_char_boundary` to advance + // past the full character to the next valid split point. + let split = name.ceil_char_boundary(pos + 1); + let (prefix, suffix) = name.split_at(split); (prefix, suffix.parse().ok()) } } From 8f7af978eec77c7dae8c99b738713e90c9b40a03 Mon Sep 17 00:00:00 2001 From: Albab-Hasan Date: Tue, 10 Mar 2026 21:08:45 +0600 Subject: [PATCH 26/30] fix: implement naming convention validation for `union` types. generated with ai assistance (claude sonnet 4.6). --- .../hir-ty/src/diagnostics/decl_check.rs | 96 ++++++++++++++++++- .../src/handlers/incorrect_case.rs | 42 ++++++++ 2 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index 29da1b0c513b..0931b859656f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -17,7 +17,7 @@ use hir_def::{ AdtId, ConstId, EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, - ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, attrs::AttrFlags, + ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId, attrs::AttrFlags, db::DefDatabase, hir::Pat, item_tree::FieldsShape, signatures::StaticFlags, src::HasSource, }; use hir_expand::{ @@ -77,6 +77,7 @@ pub enum IdentType { Structure, Trait, TypeAlias, + Union, Variable, Variant, } @@ -94,6 +95,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { IdentType::Structure => "Structure", IdentType::Trait => "Trait", IdentType::TypeAlias => "Type alias", + IdentType::Union => "Union", IdentType::Variable => "Variable", IdentType::Variant => "Variant", }; @@ -146,9 +148,7 @@ fn validate_adt(&mut self, adt: AdtId) { match adt { AdtId::StructId(struct_id) => self.validate_struct(struct_id), AdtId::EnumId(enum_id) => self.validate_enum(enum_id), - AdtId::UnionId(_) => { - // FIXME: Unions aren't yet supported by this validator. - } + AdtId::UnionId(union_id) => self.validate_union(union_id), } } @@ -383,6 +383,94 @@ fn validate_struct_fields(&mut self, struct_id: StructId) { } } + fn validate_union(&mut self, union_id: UnionId) { + // Check the union name. + let data = self.db.union_signature(union_id); + + // rustc implementation excuses repr(C) since C unions predominantly don't + // use camel case. + let has_repr_c = AttrFlags::repr(self.db, union_id.into()).is_some_and(|repr| repr.c()); + if !has_repr_c { + self.create_incorrect_case_diagnostic_for_item_name( + union_id, + &data.name, + CaseType::UpperCamelCase, + IdentType::Union, + ); + } + + // Check the field names. + self.validate_union_fields(union_id); + } + + /// Check incorrect names for union fields. + fn validate_union_fields(&mut self, union_id: UnionId) { + let data = union_id.fields(self.db); + let edition = self.edition(union_id); + let mut union_fields_replacements = data + .fields() + .iter() + .filter_map(|(_, field)| { + to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map( + |new_name| Replacement { + current_name: field.name.clone(), + suggested_text: new_name, + expected_case: CaseType::LowerSnakeCase, + }, + ) + }) + .peekable(); + + // XXX: Only look at sources if we do have incorrect names. + if union_fields_replacements.peek().is_none() { + return; + } + + let union_loc = union_id.lookup(self.db); + let union_src = union_loc.source(self.db); + + let Some(union_fields_list) = union_src.value.record_field_list() else { + always!( + union_fields_replacements.peek().is_none(), + "Replacements ({:?}) were generated for a union fields \ + which had no fields list: {:?}", + union_fields_replacements.collect::>(), + union_src + ); + return; + }; + let mut union_fields_iter = union_fields_list.fields(); + for field_replacement in union_fields_replacements { + // We assume that parameters in replacement are in the same order as in the + // actual params list, but just some of them (ones that named correctly) are skipped. + let field = loop { + if let Some(field) = union_fields_iter.next() { + let Some(field_name) = field.name() else { + continue; + }; + if field_name.as_name() == field_replacement.current_name { + break field; + } + } else { + never!( + "Replacement ({:?}) was generated for a union field \ + which was not found: {:?}", + field_replacement, + union_src + ); + return; + } + }; + + self.create_incorrect_case_diagnostic_for_ast_node( + field_replacement, + union_src.file_id, + &field, + IdentType::Field, + ); + } + } + fn validate_enum(&mut self, enum_id: EnumId) { // Check the enum name. let data = self.db.enum_signature(enum_id); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index c47449f2593d..5410f8b58a9a 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -262,6 +262,48 @@ struct SomeStruct { SomeField: u8 } ); } + #[test] + fn incorrect_union_names() { + check_diagnostics( + r#" +union non_camel_case_name { field: u8 } + // ^^^^^^^^^^^^^^^^^^^ 💡 warn: Union `non_camel_case_name` should have UpperCamelCase name, e.g. `NonCamelCaseName` + +union SCREAMING_CASE { field: u8 } + // ^^^^^^^^^^^^^^ 💡 warn: Union `SCREAMING_CASE` should have UpperCamelCase name, e.g. `ScreamingCase` +"#, + ); + } + + #[test] + fn no_diagnostic_for_camel_cased_acronyms_in_union_name() { + check_diagnostics( + r#" +union AABB { field: u8 } +"#, + ); + } + + #[test] + fn no_diagnostic_for_repr_c_union() { + check_diagnostics( + r#" +#[repr(C)] +union my_union { field: u8 } +"#, + ); + } + + #[test] + fn incorrect_union_field() { + check_diagnostics( + r#" +union SomeUnion { SomeField: u8 } + // ^^^^^^^^^ 💡 warn: Field `SomeField` should have snake_case name, e.g. `some_field` +"#, + ); + } + #[test] fn incorrect_enum_names() { check_diagnostics( From 32a35bcb426d433f84b7d0ba816bcee61cd889b4 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 10 Mar 2026 19:12:12 +0100 Subject: [PATCH 27/30] Remove outdated comment --- src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index 92ddd7fa8b07..424655ed651b 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -208,7 +208,6 @@ pub(crate) fn fixup_syntax( ]); } }, - // FIXME: foo:: ast::MatchExpr(it) => { if it.expr().is_none() { let match_token = match it.match_token() { From 23c24d2bffe202cd38d9b45c54b14984f3b55072 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 12 Mar 2026 11:43:59 +0000 Subject: [PATCH 28/30] fix: Clarify error messages when proc-macro-srv changes working directory I'm investigating issues where users see a load of logs of the form: ``` Failed to set the current working dir to /redacted/path. Error: Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` This is tricky to debug because there's two different code paths that write exactly the same error message. Ensure they're unique. --- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index 734cb4ecc169..0bdc379cb626 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -328,7 +328,7 @@ fn apply( let prev_working_dir = std::env::current_dir().ok(); if let Err(err) = std::env::set_current_dir(dir) { eprintln!( - "Failed to set the current working dir to {}. Error: {err:?}", + "Failed to change the current working dir to {}. Error: {err:?}", dir.display() ) } @@ -370,7 +370,7 @@ fn drop(&mut self) { && let Err(err) = std::env::set_current_dir(dir) { eprintln!( - "Failed to set the current working dir to {}. Error: {:?}", + "Failed to change the current working dir back to {}. Error: {:?}", dir.display(), err ) From b2d9051502c662298cf1a599ec31c11e0afcba65 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 14 Mar 2026 17:02:45 +0100 Subject: [PATCH 29/30] internal: Refactor `MirLowerCtx` to use `&ExpressionStore` instead of `&Body` --- .../crates/hir-ty/src/consteval.rs | 4 +- .../rust-analyzer/crates/hir-ty/src/mir.rs | 5 +- .../crates/hir-ty/src/mir/lower.rs | 118 +++++++++++------- .../crates/hir-ty/src/mir/lower/as_place.rs | 4 +- .../hir-ty/src/mir/lower/pattern_matching.rs | 15 ++- 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 5bc2446fdd2d..07e9f70faea6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -28,7 +28,7 @@ traits::StoredParamEnvAndCrate, }; -use super::mir::{interpret_mir, lower_to_mir, pad16}; +use super::mir::{interpret_mir, lower_body_to_mir, pad16}; pub fn unknown_const<'db>(_ty: Ty<'db>) -> Const<'db> { Const::new(DbInterner::conjure(), rustc_type_ir::ConstKind::Error(ErrorGuaranteed)) @@ -333,7 +333,7 @@ fn has_closure(body: &Body, expr: ExprId) -> bool { return c; } } - if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr) + if let Ok(mir_body) = lower_body_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr) && let Ok((Ok(result), _)) = interpret_mir(ctx.db, Arc::new(mir_body), true, None) { return result; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index 664238601108..bf17c784682c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -40,7 +40,10 @@ pub use eval::{ Evaluator, MirEvalError, VTableMap, interpret_mir, pad16, render_const_using_debug_impl, }; -pub use lower::{MirLowerError, lower_to_mir, mir_body_for_closure_query, mir_body_query}; +pub use lower::{ + MirLowerError, lower_body_to_mir, lower_to_mir_with_store, mir_body_for_closure_query, + mir_body_query, +}; pub use monomorphization::{ monomorphized_mir_body_for_closure_query, monomorphized_mir_body_query, }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 8d5e5c2e6e74..2e849bcf3a12 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -82,7 +82,7 @@ struct MirLowerCtx<'a, 'db> { labeled_loop_blocks: FxHashMap, discr_temp: Option, db: &'db dyn HirDatabase, - body: &'a Body, + store: &'a ExpressionStore, infer: &'a InferenceResult, types: &'db crate::next_solver::DefaultAny<'db>, resolver: Resolver<'db>, @@ -285,7 +285,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn new( db: &'db dyn HirDatabase, owner: DefWithBodyId, - body: &'a Body, + store: &'a ExpressionStore, infer: &'a InferenceResult, ) -> Self { let mut basic_blocks = Arena::new(); @@ -316,7 +316,7 @@ fn new( result: mir, db, infer, - body, + store, types: crate::next_solver::default_types(db), owner, resolver, @@ -354,7 +354,7 @@ fn lower_expr_to_some_operand( current: BasicBlockId, ) -> Result<'db, Option<(Operand, BasicBlockId)>> { if !self.has_adjustments(expr_id) - && let Expr::Literal(l) = &self.body[expr_id] + && let Expr::Literal(l) = &self.store[expr_id] { let ty = self.expr_ty_without_adjust(expr_id); return Ok(Some((self.lower_literal_to_operand(ty, l)?, current))); @@ -461,7 +461,7 @@ fn lower_expr_to_place_without_adjust( place: Place, mut current: BasicBlockId, ) -> Result<'db, Option> { - match &self.body[expr_id] { + match &self.store[expr_id] { Expr::OffsetOf(_) => { not_supported!("builtin#offset_of") } @@ -500,7 +500,7 @@ fn lower_expr_to_place_without_adjust( } else { let resolver_guard = self.resolver.update_to_inner_scope(self.db, self.owner, expr_id); - let hygiene = self.body.expr_path_hygiene(expr_id); + let hygiene = self.store.expr_path_hygiene(expr_id); let result = self .resolver .resolve_path_in_value_ns_fully(self.db, p, hygiene) @@ -509,7 +509,7 @@ fn lower_expr_to_place_without_adjust( self.db, p, DisplayTarget::from_crate(self.db, self.krate()), - self.body, + self.store, ) })?; self.resolver.reset_to_guard(resolver_guard); @@ -882,7 +882,7 @@ fn lower_expr_to_place_without_adjust( let variant_id = self.infer.variant_resolution_for_expr(expr_id).ok_or_else(|| match path { Some(p) => MirLowerError::UnresolvedName( - hir_display_with_store(&**p, self.body) + hir_display_with_store(&**p, self.store) .display(self.db, self.display_target()) .to_string(), ), @@ -1382,7 +1382,7 @@ fn lower_expr_to_place_without_adjust( } fn push_field_projection(&mut self, place: &mut Place, expr_id: ExprId) -> Result<'db, ()> { - if let Expr::Field { expr, name } = &self.body[expr_id] { + if let Expr::Field { expr, name } = &self.store[expr_id] { if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind() { let index = name.as_tuple_index().ok_or(MirLowerError::TypeError("named field on tuple"))? @@ -1411,7 +1411,7 @@ fn lower_literal_or_const_to_operand( ty: Ty<'db>, loc: &ExprId, ) -> Result<'db, Operand> { - match &self.body[*loc] { + match &self.store[*loc] { Expr::Literal(l) => self.lower_literal_to_operand(ty, l), Expr::Path(c) => { let owner = self.owner; @@ -1421,7 +1421,7 @@ fn lower_literal_or_const_to_operand( self.db, c, DisplayTarget::from_crate(db, owner.krate(db)), - self.body, + self.store, ) }; let pr = self @@ -1859,7 +1859,7 @@ fn lower_block_to_place( } } else { let mut err = None; - self.body.walk_bindings_in_pat(*pat, |b| { + self.store.walk_bindings_in_pat(*pat, |b| { if let Err(e) = self.push_storage_live(b, current) { err = Some(e); } @@ -1913,9 +1913,9 @@ fn lower_params_and_bindings( self.result.param_locals.extend(params.clone().map(|(it, ty)| { let local_id = self.result.locals.alloc(Local { ty: ty.store() }); self.drop_scopes.last_mut().unwrap().locals.push(local_id); - if let Pat::Bind { id, subpat: None } = self.body[it] + if let Pat::Bind { id, subpat: None } = self.store[it] && matches!( - self.body[id].mode, + self.store[id].mode, BindingAnnotation::Unannotated | BindingAnnotation::Mutable ) { @@ -1924,7 +1924,7 @@ fn lower_params_and_bindings( local_id })); // and then rest of bindings - for (id, _) in self.body.bindings() { + for (id, _) in self.store.bindings() { if !pick_binding(id) { continue; } @@ -1953,7 +1953,7 @@ fn lower_params_and_bindings( .into_iter() .skip(base_param_count + self_binding.is_some() as usize); for ((param, _), local) in params.zip(local_params) { - if let Pat::Bind { id, .. } = self.body[param] + if let Pat::Bind { id, .. } = self.store[param] && local == self.binding_local(id)? { continue; @@ -2115,7 +2115,7 @@ pub fn mir_body_for_closure_query<'db>( implementation_error!("closure expression is not closure"); }; let (captures, kind) = infer.closure_info(closure); - let mut ctx = MirLowerCtx::new(db, owner, &body, infer); + let mut ctx = MirLowerCtx::new(db, owner, &body.store, infer); // 0 is return local ctx.result.locals.alloc(Local { ty: infer.expr_ty(*root).store() }); let closure_local = ctx.result.locals.alloc(Local { @@ -2205,7 +2205,7 @@ pub fn mir_body_for_closure_query<'db>( .result .binding_locals .into_iter() - .filter(|it| ctx.body.binding_owner(it.0) == Some(expr)) + .filter(|it| ctx.store.binding_owner(it.0) == Some(expr)) .collect(); if let Some(err) = err { return Err(MirLowerError::UnresolvedUpvar(err)); @@ -2245,7 +2245,7 @@ pub fn mir_body_query<'db>( let _p = tracing::info_span!("mir_body_query", ?detail).entered(); let body = db.body(def); let infer = InferenceResult::for_body(db, def); - let mut result = lower_to_mir(db, def, &body, infer, body.body_expr)?; + let mut result = lower_body_to_mir(db, def, &body, infer, body.body_expr)?; result.shrink_to_fit(); Ok(Arc::new(result)) } @@ -2258,44 +2258,74 @@ pub(crate) fn mir_body_cycle_result<'db>( Err(MirLowerError::Loop) } -pub fn lower_to_mir<'db>( +/// Extracts params from `body.params`/`body.self_param` and the callable signature, +/// then delegates to [`lower_to_mir_with_store`]. +pub fn lower_body_to_mir<'db>( db: &'db dyn HirDatabase, owner: DefWithBodyId, body: &Body, infer: &InferenceResult, - // FIXME: root_expr should always be the body.body_expr, but since `X` in `[(); X]` doesn't have its own specific body yet, we - // need to take this input explicitly. + // FIXME: root_expr should always be the body.body_expr, + // but this is currently also used for `X` in `[(); X]` which live in the same expression store root_expr: ExprId, +) -> Result<'db, MirBody> { + let is_root = root_expr == body.body_expr; + // Extract params and self_param only when lowering the body's root expression for a function. + if is_root && let DefWithBodyId::FunctionId(fid) = owner { + let callable_sig = + db.callable_item_signature(fid.into()).instantiate_identity().skip_binder(); + let mut param_tys = callable_sig.inputs().iter().copied(); + let self_param = body.self_param.and_then(|id| Some((id, param_tys.next()?))); + + lower_to_mir_with_store( + db, + owner, + &body.store, + infer, + root_expr, + body.params.iter().copied().zip(param_tys), + self_param, + is_root, + ) + } else { + lower_to_mir_with_store( + db, + owner, + &body.store, + infer, + root_expr, + iter::empty(), + None, + is_root, + ) + } +} + +/// # Parameters +/// - `is_root`: `true` when `root_expr` is the body's top-level expression (picks +/// bindings with no owner); `false` when lowering an inline const or anonymous +/// const (picks bindings owned by `root_expr`). +pub fn lower_to_mir_with_store<'db>( + db: &'db dyn HirDatabase, + owner: DefWithBodyId, + store: &ExpressionStore, + infer: &InferenceResult, + root_expr: ExprId, + params: impl Iterator)> + Clone, + self_param: Option<(BindingId, Ty<'db>)>, + is_root: bool, ) -> Result<'db, MirBody> { if infer.type_mismatches().next().is_some() || infer.is_erroneous() { return Err(MirLowerError::HasErrors); } - let mut ctx = MirLowerCtx::new(db, owner, body, infer); + let mut ctx = MirLowerCtx::new(db, owner, store, infer); // 0 is return local ctx.result.locals.alloc(Local { ty: ctx.expr_ty_after_adjustments(root_expr).store() }); let binding_picker = |b: BindingId| { - let owner = ctx.body.binding_owner(b); - if root_expr == body.body_expr { owner.is_none() } else { owner == Some(root_expr) } - }; - // 1 to param_len is for params - // FIXME: replace with let chain once it becomes stable - let current = 'b: { - if body.body_expr == root_expr { - // otherwise it's an inline const, and has no parameter - if let DefWithBodyId::FunctionId(fid) = owner { - let callable_sig = - db.callable_item_signature(fid.into()).instantiate_identity().skip_binder(); - let mut params = callable_sig.inputs().iter().copied(); - let self_param = body.self_param.and_then(|id| Some((id, params.next()?))); - break 'b ctx.lower_params_and_bindings( - body.params.iter().zip(params).map(|(it, y)| (*it, y)), - self_param, - binding_picker, - )?; - } - } - ctx.lower_params_and_bindings([].into_iter(), None, binding_picker)? + let owner = ctx.store.binding_owner(b); + if is_root { owner.is_none() } else { owner == Some(root_expr) } }; + let current = ctx.lower_params_and_bindings(params, self_param, binding_picker)?; if let Some(current) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { let current = ctx.pop_drop_scope_assert_finished(current, root_expr.into())?; ctx.set_terminator(current, TerminatorKind::Return, root_expr.into()); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs index cf05ec27ac37..17dc95fb248a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs @@ -137,11 +137,11 @@ pub(super) fn lower_expr_as_place_without_adjust( } this.lower_expr_to_some_place_without_adjust(expr_id, current) }; - match &self.body[expr_id] { + match &self.store[expr_id] { Expr::Path(p) => { let resolver_guard = self.resolver.update_to_inner_scope(self.db, self.owner, expr_id); - let hygiene = self.body.expr_path_hygiene(expr_id); + let hygiene = self.store.expr_path_hygiene(expr_id); let resolved = self.resolver.resolve_path_in_value_ns_fully(self.db, p, hygiene); self.resolver.reset_to_guard(resolver_guard); let Some(pr) = resolved else { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs index 83139821e3b8..99c5f0fc653f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -131,7 +131,7 @@ fn pattern_match_inner( .collect::>() .into(), ); - Ok(match &self.body[pattern] { + Ok(match &self.store[pattern] { Pat::Missing => return Err(MirLowerError::IncompletePattern), Pat::Wild => (current, current_else), Pat::Tuple { args, ellipsis } => { @@ -322,7 +322,7 @@ fn pattern_match_inner( } if let &Some(slice) = slice && mode != MatchingMode::Check - && let Pat::Bind { id, subpat: _ } = self.body[slice] + && let Pat::Bind { id, subpat: _ } = self.store[slice] { let next_place = cond_place.project( ProjectionElem::Subslice { @@ -363,9 +363,14 @@ fn pattern_match_inner( )?, None => { let unresolved_name = || { - MirLowerError::unresolved_path(self.db, p, self.display_target(), self.body) + MirLowerError::unresolved_path( + self.db, + p, + self.display_target(), + self.store, + ) }; - let hygiene = self.body.pat_path_hygiene(pattern); + let hygiene = self.store.pat_path_hygiene(pattern); let pr = self .resolver .resolve_path_in_value_ns(self.db, p, hygiene) @@ -432,7 +437,7 @@ fn pattern_match_inner( (next, Some(else_target)) } }, - Pat::Lit(l) => match &self.body[*l] { + Pat::Lit(l) => match &self.store[*l] { Expr::Literal(l) => { if mode == MatchingMode::Check { let c = self.lower_literal_to_operand(self.infer.pat_ty(pattern), l)?; From 491852c7700c53e6a3779b3ea0e1d4d3197445a7 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 14 Mar 2026 20:22:14 +0200 Subject: [PATCH 30/30] Infer generic args for trait ref and its assoc type This was already there before the switch to the new solver, but it was reverted during the switch for some reason. --- .../crates/hir-ty/src/lower/path.rs | 4 ++-- .../src/handlers/incorrect_generics_len.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index 79f29d370f5c..81a944128d23 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -183,7 +183,7 @@ pub(crate) fn lower_partly_resolved_path( let trait_ref = self.lower_trait_ref_from_resolved_path( trait_, Ty::new_error(self.ctx.interner, ErrorGuaranteed), - false, + infer_args, ); tracing::debug!(?trait_ref); self.skip_resolved_segment(); @@ -201,7 +201,7 @@ pub(crate) fn lower_partly_resolved_path( // this point (`trait_ref.substitution`). let substitution = self.substs_from_path_segment( associated_ty.into(), - false, + infer_args, None, true, ); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs index 894e044642cc..25220704e04d 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs @@ -220,6 +220,23 @@ fn enum_type_alias_default_param() { fn main() { let _ = Result::<()>::Ok(()); +} + "#, + ); + } + + #[test] + fn type_as_trait_does_not_count() { + check_diagnostics( + r#" +pub trait Lock { + fn new(b: T) -> Self; +} +pub trait LockChoice { + type Lock: Lock; +} +fn f() { + ::Lock::new(()); } "#, );