From 8535baa02181efad7020dd5b9d09e8f5f805eeeb Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 13 Aug 2026 23:01:34 +0800 Subject: [PATCH 01/26] fix: don't error on tail comma for some macro Example --- ```rust const _: &str = env!("PATH",); ``` **Before this PR** ``` /* expand error: expected string literal */ ``` **After this PR** ```rust const _: &str = "/usr/bin:/bin"; ``` --- .../macro_expansion_tests/builtin_fn_macro.rs | 14 +++++++++-- .../crates/hir-expand/src/builtin/fn_macro.rs | 23 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 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 46cdb39c5b46b..d6ccf9ca51ab0 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 @@ -132,13 +132,23 @@ fn test_env_expand() { #[rustc_builtin_macro] macro_rules! env {() => {}} -fn main() { env!("TEST_ENV_VAR"); } +fn main() { + env!("TEST_ENV_VAR"); + env!("TEST_ENV_VAR",); + env!("TEST_ENV_VAR", "error"); + env!("TEST_ENV_VAR", "error",); +} "#, expect![[r##" #[rustc_builtin_macro] macro_rules! env {() => {}} -fn main() { "UNRESOLVED_ENV_VAR"; } +fn main() { + "UNRESOLVED_ENV_VAR"; + "UNRESOLVED_ENV_VAR"; + "UNRESOLVED_ENV_VAR"; + "UNRESOLVED_ENV_VAR"; +} "##]], ); } 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 b173f44f34ab1..7a162fcf4bcb7 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 @@ -767,7 +767,26 @@ fn relative_file( } fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { - let mut tt = TtElement::Subtree(tt.top_subtree(), tt.iter()); + let expect_literal = |span| ExpandError::other(span, "expected string literal"); + let mut tt = { + let mut tt_iter = tt.iter(); + let extracted = + tt_iter.next().ok_or_else(|| expect_literal(tt.top_subtree().delimiter.close))?; + + match tt_iter.next() { + None => {} + Some(TtElement::Leaf(tt::Leaf::Punct(it))) if it.char == ',' => { + // Tail comma + // FIXME: Ignored like env!("NAME", "compile_error message") + } + Some(tt) => { + return Err(ExpandError::other(tt.first_span(), "unexpected input")); + } + } + + extracted + }; + (|| { // FIXME: We wrap expression fragments in parentheses which can break this expectation // here @@ -795,7 +814,7 @@ fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { TtElement::Subtree(tt, _) => Err(tt.delimiter.open.cover(tt.delimiter.close)), } })() - .map_err(|span| ExpandError::other(span, "expected string literal")) + .map_err(expect_literal) } fn include_expand( From 84e7c4c29bbebe06b4563e62a9eee3624dafcc57 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 9 Aug 2026 04:35:47 +0300 Subject: [PATCH 02/26] Optimize the heck out of the storage of token trees This is basically the most optimized (memory-wise) storage possible, found after multiple measurements. The price we pay for this ultra-extra optimization is a bunch of unsafe, encapsulated in `tt/src/storage.rs`. Since macros and therefore token trees are so common in Rust code, I think this is worth it. Some stats: - On rust-analyzer itself, memory usage is reduced by 30mb. rust-analyzer doesn't use macros a lot and the previous optimization already took the most, but when considering that *all* token trees in r-a now consumes only about 40mb, this is still surprising. - On buck2, ~143mb is saved. - On omicron, ~436mb is saved, and this is after the previous optimization already ripped 880mb! It is only using ~180mb for token trees now, in total! The basic idea is to use a variable-length encoding into a bytes array. Multiple measurements were done in order to determine the most common forms of token trees along with their frequencies, and to find the best encoding. In addition, we also now sort the compressed spans by their frequencies (in a descending order), so that even if a `TopSubtree` has more than 2^4 unique compressed spans, we will still use the more efficient encoding for the biggest number of spans possible. This is made possible by the fact that unlike the previous encoding, now we don't force one span encoding for all tokens (or in fact even for the two spans in one subtree). --- src/tools/rust-analyzer/Cargo.lock | 1 - src/tools/rust-analyzer/crates/cfg/Cargo.toml | 5 +- .../crates/hir-expand/src/fixup.rs | 8 +- .../rust-analyzer/crates/intern/src/symbol.rs | 29 +- src/tools/rust-analyzer/crates/mbe/src/lib.rs | 2 +- src/tools/rust-analyzer/crates/tt/Cargo.toml | 1 - .../rust-analyzer/crates/tt/src/buffer.rs | 99 +- src/tools/rust-analyzer/crates/tt/src/iter.rs | 88 +- src/tools/rust-analyzer/crates/tt/src/lib.rs | 243 +-- .../rust-analyzer/crates/tt/src/storage.rs | 1875 ++++++++++------- 10 files changed, 1351 insertions(+), 1000 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 19bdd0c7635a5..f1ea05a5ec366 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -3096,7 +3096,6 @@ name = "tt" version = "0.0.0" dependencies = [ "arrayvec", - "indexmap", "intern", "ra-ap-rustc_lexer", "rustc-hash 2.1.2", diff --git a/src/tools/rust-analyzer/crates/cfg/Cargo.toml b/src/tools/rust-analyzer/crates/cfg/Cargo.toml index 15de1f329385d..7759fc4ae1948 100644 --- a/src/tools/rust-analyzer/crates/cfg/Cargo.toml +++ b/src/tools/rust-analyzer/crates/cfg/Cargo.toml @@ -28,10 +28,9 @@ arbitrary = { version = "1.4.1", features = ["derive"] } # local deps syntax-bridge.workspace = true -syntax.workspace = true -# tt is needed for testing -cfg = { path = ".", default-features = false, features = ["tt"] } +# tt and syntax are needed for testing +cfg = { path = ".", default-features = false, features = ["tt", "syntax"] } [features] default = [] 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 939104b709163..8781358822064 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -419,9 +419,11 @@ mod tests { } fn check_subtree_eq(a: &tt::TopSubtree, b: &tt::TopSubtree) -> bool { - let a = a.view().as_token_trees().iter_flat_tokens(); - let b = b.view().as_token_trees().iter_flat_tokens(); - a.len() == b.len() && std::iter::zip(a, b).all(|(a, b)| check_tt_eq(&a, &b)) + let a = a.view().as_token_trees(); + let b = b.view().as_token_trees(); + a.len() == b.len() + && std::iter::zip(a.iter_flat_tokens(), b.iter_flat_tokens()) + .all(|(a, b)| check_tt_eq(&a, &b)) } fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool { diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol.rs b/src/tools/rust-analyzer/crates/intern/src/symbol.rs index 72d32d1017747..cf41db85a163d 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol.rs @@ -174,6 +174,19 @@ impl Symbol { self.repr.as_str() } + #[inline] + pub fn into_raw(self) -> NonNull<*const str> { + ManuallyDrop::new(self).repr.packed + } + + /// # Safety + /// + /// The pointer must have come from [`Symbol::into_raw()`]. + #[inline] + pub unsafe fn from_raw(ptr: NonNull<*const str>) -> Symbol { + Symbol { repr: TaggedArcPtr { packed: ptr } } + } + #[inline] fn select_shard( storage: &'static Map, @@ -217,11 +230,12 @@ impl Symbol { shard.shrink_to(len, |(x, _)| Self::hash(storage, x.as_str())); } } -} -impl Drop for Symbol { + /// # Safety + /// + /// You must know that you have a `Symbol` instance that won't be dropped, so decreasing the refcount is valid. #[inline] - fn drop(&mut self) { + pub unsafe fn decrease_refcount(&mut self) { // SAFETY: We're dropping, we have ownership. let Some(arc) = (unsafe { self.repr.try_as_arc_owned() }) else { return; @@ -237,6 +251,15 @@ impl Drop for Symbol { } } +impl Drop for Symbol { + #[inline] + fn drop(&mut self) { + unsafe { + self.decrease_refcount(); + } + } +} + impl Clone for Symbol { fn clone(&self) -> Self { Self { repr: increase_arc_refcount(self.repr) } diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs index 76fdac097ff71..6de9b4275ce2d 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs @@ -442,7 +442,7 @@ pub fn expect_fragment<'t>( } let res = cursor.crossed(); - tt_iter.flat_advance(res.len()); + tt_iter.flat_advance_to(&cursor); ExpandResult { value: res, err } } diff --git a/src/tools/rust-analyzer/crates/tt/Cargo.toml b/src/tools/rust-analyzer/crates/tt/Cargo.toml index 9a798b592d03c..bd8f740b2f50d 100644 --- a/src/tools/rust-analyzer/crates/tt/Cargo.toml +++ b/src/tools/rust-analyzer/crates/tt/Cargo.toml @@ -16,7 +16,6 @@ doctest = false arrayvec.workspace = true text-size.workspace = true rustc-hash.workspace = true -indexmap.workspace = true span = { path = "../span", version = "0.0", default-features = false } stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs index 78cf4b956d0ce..03dcb1b405378 100644 --- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs +++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs @@ -1,41 +1,51 @@ //! Stateful iteration over token trees. //! //! We use this as the source of tokens for parser. -use crate::{Leaf, Subtree, TokenTree, TokenTreesView, dispatch_ref}; +use crate::{Leaf, Subtree, TokenTree, TokenTreesView, storage::TokenTreesSlice}; pub struct Cursor<'a> { - buffer: TokenTreesView<'a>, - index: usize, - subtrees_stack: Vec, + origin: TokenTreesSlice<'a>, + buffer_before_current: TokenTreesSlice<'a>, + buffer_after_current: TokenTreesSlice<'a>, + /// The number of times we called [`Self::advance()`]. Also the index of [`Self::next`]. + advances_count: usize, + len: usize, + next: Option, + subtrees_stack: Vec<(usize, Subtree)>, } impl<'a> Cursor<'a> { - pub fn new(buffer: TokenTreesView<'a>) -> Self { - Self { buffer, index: 0, subtrees_stack: Vec::new() } + pub fn new(origin: TokenTreesView<'a>) -> Self { + let mut buffer_after_current = origin.slice; + let buffer_before_current = buffer_after_current; + let len = origin.len; + let next = if len >= 1 { buffer_after_current.advance() } else { None }; + Self { + origin: origin.slice, + buffer_after_current, + buffer_before_current, + advances_count: 0, + len, + next, + subtrees_stack: Vec::new(), + } } /// Check whether it is eof pub fn eof(&self) -> bool { - self.index == self.buffer.len() && self.subtrees_stack.is_empty() + self.next.is_none() && self.subtrees_stack.is_empty() } pub fn is_root(&self) -> bool { self.subtrees_stack.is_empty() } - fn at(&self, idx: usize) -> Option { - dispatch_ref! { - match self.buffer.repr => tt => Some(tt.get(idx)?.to_api(self.buffer.span_parts)) - } + fn last_subtree(&self) -> Option<(usize, Subtree)> { + self.subtrees_stack.last().copied() } - fn last_subtree(&self) -> Option<(usize, Subtree)> { - self.subtrees_stack.last().map(|&subtree_idx| { - let Some(TokenTree::Subtree(subtree)) = self.at(subtree_idx) else { - panic!("subtree pointing to non-subtree"); - }; - (subtree_idx, subtree) - }) + pub(crate) fn remaining(&self) -> TokenTreesView<'a> { + TokenTreesView { slice: self.buffer_before_current, len: self.len - self.advances_count } } pub fn end(&mut self) -> Subtree { @@ -44,7 +54,7 @@ impl<'a> Cursor<'a> { // +1 because `Subtree.len` excludes the subtree itself. assert_eq!( last_subtree_idx + last_subtree.usize_len() + 1, - self.index, + self.advances_count, "called `Cursor::end()` without finishing a subtree" ); self.subtrees_stack.pop(); @@ -55,11 +65,24 @@ impl<'a> Cursor<'a> { pub fn token_tree(&self) -> Option { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. - if last_subtree_idx + last_subtree.usize_len() + 1 == self.index { + if last_subtree_idx + last_subtree.usize_len() + 1 == self.advances_count { return None; } } - self.at(self.index) + self.next.clone() + } + + fn advance(&mut self) { + if self.advances_count >= self.len { + return; + } + if let Some(TokenTree::Subtree(subtree)) = self.next { + self.subtrees_stack.push((self.advances_count, subtree)); + } + self.advances_count += 1; + self.buffer_before_current = self.buffer_after_current; + self.next = + if self.advances_count < self.len { self.buffer_after_current.advance() } else { None }; } /// Bump the cursor, and enters a subtree if it is on one. @@ -68,40 +91,35 @@ impl<'a> Cursor<'a> { // +1 because `Subtree.len` excludes the subtree itself. assert_ne!( last_subtree_idx + last_subtree.usize_len() + 1, - self.index, + self.advances_count, "called `Cursor::bump()` when at the end of a subtree" ); } - if let Some(TokenTree::Subtree(_)) = self.at(self.index) { - self.subtrees_stack.push(self.index); - } - self.index += 1; + self.advance(); } pub fn bump_or_end(&mut self) { - if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { - // +1 because `Subtree.len` excludes the subtree itself. - if last_subtree_idx + last_subtree.usize_len() + 1 == self.index { - self.subtrees_stack.pop(); - return; - } - } // +1 because `Subtree.len` excludes the subtree itself. - if let Some(TokenTree::Subtree(_)) = self.at(self.index) { - self.subtrees_stack.push(self.index); + if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() + && last_subtree_idx + last_subtree.usize_len() + 1 == self.advances_count + { + self.subtrees_stack.pop(); + return; } - self.index += 1; + self.advance(); } pub fn peek_two_leaves(&self) -> Option<[Leaf; 2]> { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. let last_end = last_subtree_idx + last_subtree.usize_len() + 1; - if last_end == self.index || last_end == self.index + 1 { + if last_end == self.advances_count || last_end == self.advances_count + 1 { return None; } } - self.at(self.index).zip(self.at(self.index + 1)).and_then(|it| match it { + let mut buffer = self.buffer_after_current; + let next_next = if self.advances_count + 1 < self.len { buffer.advance() } else { None }; + self.next.clone().zip(next_next).and_then(|it| match it { (TokenTree::Leaf(a), TokenTree::Leaf(b)) => Some([a, b]), _ => None, }) @@ -109,9 +127,6 @@ impl<'a> Cursor<'a> { pub fn crossed(&self) -> TokenTreesView<'a> { assert!(self.is_root()); - TokenTreesView { - repr: self.buffer.repr.get(..self.index).unwrap(), - span_parts: self.buffer.span_parts, - } + TokenTreesView { slice: self.origin, len: self.advances_count } } } diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index 7caacd40dd7e3..9419a9e8534eb 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -8,8 +8,8 @@ use intern::sym; use span::Span; use crate::{ - Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesReprRef, - TokenTreesView, dispatch_ref, + Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesView, + buffer::Cursor, }; #[derive(Clone)] @@ -126,13 +126,13 @@ impl<'a> TtIter<'a> { return Ok(res); } - let (second, third) = match (self.peek_n(0), self.peek_n(1)) { - (Some(TokenTree::Leaf(Leaf::Punct(p2))), Some(TokenTree::Leaf(Leaf::Punct(p3)))) + let (second, third) = match self.peek_two() { + [Some(TokenTree::Leaf(Leaf::Punct(p2))), Some(TokenTree::Leaf(Leaf::Punct(p3)))] if p2.spacing == Spacing::Joint => { (p2, Some(p3)) } - (Some(TokenTree::Leaf(Leaf::Punct(p2))), _) => (p2, None), + [Some(TokenTree::Leaf(Leaf::Punct(p2))), _] => (p2, None), _ => { res.push(first); return Ok(res); @@ -165,20 +165,21 @@ impl<'a> TtIter<'a> { } /// This method won't check for subtrees, so the nth token tree may not be the nth sibling of the current tree. - fn peek_n(&self, n: usize) -> Option { - dispatch_ref! { - match self.inner.repr => tt => Some(tt.get(n)?.to_api(self.inner.span_parts)) - } + fn peek_two(&self) -> [Option; 2] { + let mut iter = self.inner.iter_flat_tokens(); + [iter.next(), iter.next()] } pub fn peek(&self) -> Option> { - match self.peek_n(0)? { + if self.inner.is_empty() { + return None; + } + let mut slice = self.inner.slice; + match slice.advance()? { TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), TokenTree::Subtree(subtree) => { - let nested_repr = self.inner.repr.get(1..subtree.usize_len() + 1).unwrap(); - let nested_iter = TtIter { - inner: TokenTreesView { repr: nested_repr, span_parts: self.inner.span_parts }, - }; + let nested_iter = + TtIter { inner: TokenTreesView { len: subtree.usize_len(), slice } }; Some(TtElement::Subtree(subtree, nested_iter)) } } @@ -186,7 +187,7 @@ impl<'a> TtIter<'a> { /// Equivalent to `peek().is_none()`, but a bit faster. pub fn is_empty(&self) -> bool { - self.inner.len() == 0 + self.inner.is_empty() } pub fn next_span(&self) -> Option { @@ -197,9 +198,9 @@ impl<'a> TtIter<'a> { self.inner } - /// **Warning**: This advances `skip` **flat** token trees, subtrees account for children+1! - pub fn flat_advance(&mut self, skip: usize) { - self.inner.repr = self.inner.repr.get(skip..).unwrap(); + /// **Warning**: This advances **flat** token trees, subtrees account for children+1! + pub fn flat_advance_to(&mut self, up_to: &Cursor<'a>) { + self.inner = up_to.remaining(); } pub fn savepoint(&self) -> TtIterSavepoint<'a> { @@ -207,34 +208,7 @@ impl<'a> TtIter<'a> { } pub fn from_savepoint(&self, savepoint: TtIterSavepoint<'a>) -> TokenTreesView<'a> { - let len = match (self.inner.repr, savepoint.0.repr) { - ( - TokenTreesReprRef::SpanStorage32(this), - TokenTreesReprRef::SpanStorage32(savepoint), - ) => { - (this.as_ptr() as usize - savepoint.as_ptr() as usize) - / size_of::>() - } - ( - TokenTreesReprRef::SpanStorage64(this), - TokenTreesReprRef::SpanStorage64(savepoint), - ) => { - (this.as_ptr() as usize - savepoint.as_ptr() as usize) - / size_of::>() - } - ( - TokenTreesReprRef::SpanStorage96(this), - TokenTreesReprRef::SpanStorage96(savepoint), - ) => { - (this.as_ptr() as usize - savepoint.as_ptr() as usize) - / size_of::>() - } - _ => panic!("savepoint did not originate from this TtIter"), - }; - TokenTreesView { - repr: savepoint.0.repr.get(..len).unwrap(), - span_parts: savepoint.0.span_parts, - } + TokenTreesView { slice: savepoint.0.slice, len: savepoint.0.len - self.inner.len } } pub fn next_as_view(&mut self) -> Option> { @@ -274,12 +248,20 @@ impl TtElement<'_> { impl<'a> Iterator for TtIter<'a> { type Item = TtElement<'a>; fn next(&mut self) -> Option { - let result = self.peek()?; - let skip = match &result { - TtElement::Leaf(_) => 1, - TtElement::Subtree(subtree, _) => subtree.usize_len() + 1, - }; - self.inner.repr = self.inner.repr.get(skip..).unwrap(); - Some(result) + if self.inner.is_empty() { + return None; + } + self.inner.len -= 1; + let (tt, subtree_slice) = self.inner.slice.advance_skip_subtree()?; + match tt { + TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), + TokenTree::Subtree(subtree) => { + self.inner.len -= subtree.usize_len(); + let nested_iter = TtIter { + inner: TokenTreesView { len: subtree.usize_len(), slice: subtree_slice }, + }; + Some(TtElement::Subtree(subtree, nested_iter)) + } + } } } diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 7b46c33596441..2bc2b64cd4fd0 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -17,7 +17,7 @@ pub mod buffer; pub mod iter; mod storage; -use std::{fmt, slice::SliceIndex}; +use std::fmt; use arrayvec::ArrayString; use buffer::Cursor; @@ -27,7 +27,7 @@ use stdx::{impl_from, itertools::Itertools as _}; pub use span::Span; pub use text_size::{TextRange, TextSize}; -use crate::storage::{CompressedSpanPart, SpanStorage}; +use crate::storage::TokenTreesSlice; pub use self::iter::{TtElement, TtIter}; pub use self::storage::{TopSubtree, TopSubtreeBuilder}; @@ -42,9 +42,11 @@ pub struct Lit { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(u8)] +// The discriminants are important for `storage.rs` decoding. pub enum IdentIsRaw { - No, - Yes, + No = 0, + Yes = 1, } impl IdentIsRaw { pub fn yes(self) -> bool { @@ -113,6 +115,14 @@ impl Leaf { Leaf::Ident(it) => &it.span, } } + + fn symbol(&self) -> Option<&Symbol> { + match self { + Leaf::Literal(Literal { text_and_suffix: symbol, .. }) + | Leaf::Ident(Ident { sym: symbol, .. }) => Some(symbol), + Leaf::Punct(_) => None, + } + } } impl_from!(Literal, Punct, Ident for Leaf); @@ -129,67 +139,16 @@ impl Subtree { } } -#[rust_analyzer::macro_style(braces)] -macro_rules! dispatch_ref { - ( - match $scrutinee:expr => $tt:ident => $body:expr - ) => { - match $scrutinee { - $crate::TokenTreesReprRef::SpanStorage32($tt) => $body, - $crate::TokenTreesReprRef::SpanStorage64($tt) => $body, - $crate::TokenTreesReprRef::SpanStorage96($tt) => $body, - } - }; -} -use dispatch_ref; - -#[derive(Clone, Copy)] -enum TokenTreesReprRef<'a> { - SpanStorage32(&'a [crate::storage::TokenTree]), - SpanStorage64(&'a [crate::storage::TokenTree]), - SpanStorage96(&'a [crate::storage::TokenTree]), -} - -impl<'a> TokenTreesReprRef<'a> { - #[inline] - fn get(&self, index: I) -> Option - where - I: SliceIndex< - [crate::storage::TokenTree], - Output = [crate::storage::TokenTree], - >, - I: SliceIndex< - [crate::storage::TokenTree], - Output = [crate::storage::TokenTree], - >, - I: SliceIndex< - [crate::storage::TokenTree], - Output = [crate::storage::TokenTree], - >, - { - Some(match self { - TokenTreesReprRef::SpanStorage32(tt) => { - TokenTreesReprRef::SpanStorage32(tt.get(index)?) - } - TokenTreesReprRef::SpanStorage64(tt) => { - TokenTreesReprRef::SpanStorage64(tt.get(index)?) - } - TokenTreesReprRef::SpanStorage96(tt) => { - TokenTreesReprRef::SpanStorage96(tt.get(index)?) - } - }) - } -} - #[derive(Clone, Copy)] pub struct TokenTreesView<'a> { - repr: TokenTreesReprRef<'a>, - span_parts: &'a [CompressedSpanPart], + slice: TokenTreesSlice<'a>, + len: usize, } impl<'a> TokenTreesView<'a> { + #[inline] pub fn empty() -> Self { - Self { repr: TokenTreesReprRef::SpanStorage32(&[]), span_parts: &[] } + Self { slice: TokenTreesSlice::empty(), len: 0 } } pub fn iter(&self) -> TtIter<'a> { @@ -201,9 +160,7 @@ impl<'a> TokenTreesView<'a> { } pub fn len(&self) -> usize { - dispatch_ref! { - match self.repr => tt => tt.len() - } + self.len } pub fn is_empty(&self) -> bool { @@ -211,12 +168,9 @@ impl<'a> TokenTreesView<'a> { } pub fn try_into_subtree(self) -> Option> { - let is_subtree = dispatch_ref! { - match self.repr => tt => matches!( - tt.first(), - Some(crate::storage::TokenTree::Subtree { len, .. }) if (*len as usize) == (tt.len() - 1) - ) - }; + let is_subtree = self.iter_flat_tokens().next().is_some_and( + |it| matches!(it, TokenTree::Subtree(subtree) if subtree.usize_len() == self.len - 1), + ); if is_subtree { Some(SubtreeView(self)) } else { None } } @@ -251,23 +205,29 @@ impl<'a> TokenTreesView<'a> { } pub fn first_span(&self) -> Option { - Some(dispatch_ref! { - match self.repr => tt => tt.first()?.first_span().span(self.span_parts) - }) + self.iter_flat_tokens().next().map(|it| it.first_span()) } + /// Note: this is quite expensive, this needs to decode the whole view, + /// although it "tricks" by skipping subtrees (since we know their byte length). pub fn last_span(&self) -> Option { - Some(dispatch_ref! { - match self.repr => tt => tt.last()?.last_span().span(self.span_parts) - }) + let mut iter = self.iter(); + loop { + match iter.last()? { + TtElement::Leaf(leaf) => return Some(*leaf.span()), + TtElement::Subtree(subtree, tt_iter) => { + if subtree.len == 0 { + return Some(subtree.delimiter.close); + } else { + iter = tt_iter; + } + } + } + } } - pub fn iter_flat_tokens(self) -> impl ExactSizeIterator + use<'a> { - (0..self.len()).map(move |idx| { - dispatch_ref! { - match self.repr => tt => tt[idx].to_api(self.span_parts) - } - }) + pub fn iter_flat_tokens(&self) -> impl Iterator + use<'a> { + self.slice.iter().take(self.len) } } @@ -343,23 +303,10 @@ impl<'a> SubtreeView<'a> { } pub fn top_subtree(&self) -> Subtree { - dispatch_ref! { - match self.0.repr => tt => { - let crate::storage::TokenTree::Subtree { len, delim_kind, open_span, close_span } = - &tt[0] - else { - unreachable!("the first token tree is always the top subtree"); - }; - Subtree { - delimiter: Delimiter { - open: open_span.span(self.0.span_parts), - close: close_span.span(self.0.span_parts), - kind: *delim_kind, - }, - len: *len, - } - } - } + let Some(TokenTree::Subtree(subtree)) = self.0.iter_flat_tokens().next() else { + unreachable!("the first token tree is always the top subtree"); + }; + subtree } pub fn strip_invisible(&self) -> TokenTreesView<'a> { @@ -371,18 +318,10 @@ impl<'a> SubtreeView<'a> { } pub fn token_trees(&self) -> TokenTreesView<'a> { - let repr = match self.0.repr { - TokenTreesReprRef::SpanStorage32(token_trees) => { - TokenTreesReprRef::SpanStorage32(&token_trees[1..]) - } - TokenTreesReprRef::SpanStorage64(token_trees) => { - TokenTreesReprRef::SpanStorage64(&token_trees[1..]) - } - TokenTreesReprRef::SpanStorage96(token_trees) => { - TokenTreesReprRef::SpanStorage96(&token_trees[1..]) - } - }; - TokenTreesView { repr, ..self.0 } + let mut result = self.0; + result.slice.advance(); + result.len -= 1; + result } } @@ -435,11 +374,13 @@ impl Delimiter { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(u8)] +// The discriminants are important for decoding for `storage.rs`. pub enum DelimiterKind { - Parenthesis, - Brace, - Bracket, - Invisible, + Parenthesis = 0, + Brace = 1, + Bracket = 2, + Invisible = 3, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -555,7 +496,9 @@ pub struct Punct { /// compound token. Used for conversions to `proc_macro::Spacing`. Also used to /// guide pretty-printing, which is where the `JointHidden` value (which isn't /// part of `proc_macro::Spacing`) comes in useful. +// The discriminants are important for decoding for `storage.rs`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] pub enum Spacing { /// The token cannot join with the following token to form a compound /// token. @@ -572,7 +515,7 @@ pub enum Spacing { /// /// Converts to `proc_macro::Spacing::Alone`, and /// `proc_macro::Spacing::Alone` converts back to this. - Alone, + Alone = 0, /// The token can join with the following token to form a compound token. /// @@ -586,7 +529,7 @@ pub enum Spacing { /// /// Converts to `proc_macro::Spacing::Joint`, and /// `proc_macro::Spacing::Joint` converts back to this. - Joint, + Joint = 1, /// The token can join with the following token to form a compound token, /// but this will not be visible at the proc macro level. (This is what the @@ -608,7 +551,7 @@ pub enum Spacing { /// pretty-printing of `TokenStream`'s produced by other means (i.e. parsed /// source code, internally constructed token streams, and token streams /// produced by declarative macros). - JointHidden, + JointHidden = 2, } /// Identifier or keyword. @@ -764,36 +707,16 @@ impl Subtree { } pub fn pretty(tkns: TokenTreesView<'_>) -> String { - return dispatch_ref! { - match tkns.repr => tt => pretty_impl(tkns, tt) - }; - - use crate::storage::TokenTree; + return pretty_impl(tkns.iter()); - fn tokentree_to_text( - tkns_view: TokenTreesView<'_>, - tkn: &TokenTree, - tkns: &mut &[TokenTree], - ) -> String { + fn tokentree_to_text(tkn: TtElement<'_>) -> String { match tkn { - TokenTree::Ident { sym, is_raw, .. } => format!("{}{}", is_raw.as_str(), sym), - &TokenTree::Literal { ref text_and_suffix, kind, suffix_len, span } => { - format!( - "{}", - Literal { - text_and_suffix: text_and_suffix.clone(), - span: span.span(tkns_view.span_parts), - kind, - suffix_len - } - ) + TtElement::Leaf(leaf) => { + format!("{}", leaf) } - TokenTree::Punct { char, .. } => format!("{}", char), - TokenTree::Subtree { len, delim_kind, .. } => { - let (subtree_content, rest) = tkns.split_at(*len as usize); - let content = pretty_impl(tkns_view, subtree_content); - *tkns = rest; - let (open, close) = match *delim_kind { + TtElement::Subtree(Subtree { delimiter, .. }, subtree_content) => { + let content = pretty_impl(subtree_content); + let (open, close) = match delimiter.kind { DelimiterKind::Brace => ("{", "}"), DelimiterKind::Bracket => ("[", "]"), DelimiterKind::Parenthesis => ("(", ")"), @@ -804,23 +727,16 @@ pub fn pretty(tkns: TokenTreesView<'_>) -> String { } } - fn pretty_impl( - tkns_view: TokenTreesView<'_>, - mut tkns: &[TokenTree], - ) -> String { + fn pretty_impl(tkns: TtIter<'_>) -> String { let mut last = String::new(); let mut last_to_joint = true; - while let Some((tkn, rest)) = tkns.split_first() { - tkns = rest; - last = [last, tokentree_to_text(tkns_view, tkn, &mut tkns)].join(if last_to_joint { - "" - } else { - " " - }); + for tkn in tkns { + last = + [last, tokentree_to_text(tkn.clone())].join(if last_to_joint { "" } else { " " }); last_to_joint = false; - if let TokenTree::Punct { spacing, .. } = tkn - && *spacing == Spacing::Joint + if let TtElement::Leaf(Leaf::Punct(Punct { spacing, .. })) = tkn + && spacing == Spacing::Joint { last_to_joint = true; } @@ -847,7 +763,7 @@ impl TransformTtAction<'_> { /// tts view. pub fn transform_tt<'b>( tt: &mut TopSubtree, - mut callback: impl FnMut(TokenTree) -> TransformTtAction<'b>, + mut callback: impl FnMut(&TokenTree) -> TransformTtAction<'b>, ) { let mut tt_vec = tt.as_token_trees().iter_flat_tokens().collect::>(); @@ -867,27 +783,20 @@ pub fn transform_tt<'b>( } } - let current = match &tt_vec[i] { - TokenTree::Leaf(leaf) => TokenTree::Leaf(match leaf { - Leaf::Literal(leaf) => Leaf::Literal(leaf.clone()), - Leaf::Punct(leaf) => Leaf::Punct(*leaf), - Leaf::Ident(leaf) => Leaf::Ident(leaf.clone()), - }), - TokenTree::Subtree(subtree) => TokenTree::Subtree(*subtree), - }; + let current = &tt_vec[i]; let action = callback(current); match action { TransformTtAction::Keep => { // This cannot be shared with the replaced case, because then we may push the same subtree // twice, and will update it twice which will lead to errors. - if let TokenTree::Subtree(_) = &tt_vec[i] { + if let TokenTree::Subtree(_) = current { subtrees_stack.push(i); } i += 1; } TransformTtAction::ReplaceWith(replacement) => { - let old_len = 1 + match &tt_vec[i] { + let old_len = 1 + match current { TokenTree::Leaf(_) => 0, TokenTree::Subtree(subtree) => subtree.usize_len(), }; diff --git a/src/tools/rust-analyzer/crates/tt/src/storage.rs b/src/tools/rust-analyzer/crates/tt/src/storage.rs index 50a1106175ab3..150777cc39e55 100644 --- a/src/tools/rust-analyzer/crates/tt/src/storage.rs +++ b/src/tools/rust-analyzer/crates/tt/src/storage.rs @@ -2,40 +2,32 @@ //! will waste a lot of memory. So instead we implement a clever compression mechanism: //! //! A `TopSubtree` has a list of [`CompressedSpanPart`], which are the parts of a span -//! that tend to be shared between tokens - namely, without the range. The main list -//! of token trees is kept in one of three versions, where we use the smallest version -//! we can for this tree: +//! that tend to be shared between tokens - namely, without the range. //! -//! 1. In the most common version a span is just a `u32`. The bits are divided as follows: -//! there are 4 bits that index into the [`CompressedSpanPart`] list. 20 bits -//! store the range start, and 8 bits store the range length. In experiments, -//! this accounts for 75%-85% of the spans. -//! 2. In the second version a span is 64 bits. 32 bits for the range start, 16 bits -//! for the range length, and 16 bits for the span parts index. This is used in -//! less than 2% of all `TopSubtree`s, but they account for 15%-25% of the spans: -//! those are mostly token tree munchers, that generate a lot of `SyntaxContext`s -//! (because they recurse a lot), which is why they can't fit in the first version, -//! and tend to generate a lot of code. -//! 3. The third version is practically unused; 65,535 bytes for a token and 65,535 -//! unique span parts is more than enough for everybody. However, someone may still -//! create a macro that requires more, therefore we have this version as a backup: -//! it uses 96 bits, 32 for each of the range start, length and span parts index. - -use std::fmt; +//! The main list of token trees is stored in a variable-length encoding as bytes. +//! The encoding is documented in the [`decode()`] function (which decodes one [`TokenTree`]). + +use std::{assert_matches, collections::hash_map, fmt::Debug, hint::cold_path, mem::transmute}; + +#[cfg(all(debug_assertions, not(miri)))] +use std::cell::Cell; + +#[cfg(not(all(debug_assertions, not(miri))))] +use std::mem::MaybeUninit; use intern::Symbol; -use rustc_hash::FxBuildHasher; +use rustc_hash::FxHashMap; use span::{Span, SpanAnchor, SyntaxContext, TextRange, TextSize}; use crate::{ - DelimSpan, DelimiterKind, IdentIsRaw, LitKind, Spacing, SubtreeView, TokenTreesReprRef, - TokenTreesView, TtIter, dispatch_ref, + DelimSpan, Delimiter, DelimiterKind, Ident, IdentIsRaw, Leaf, LitKind, Literal, Punct, Spacing, + Subtree, SubtreeView, TokenTree, TokenTreesView, TtIter, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct CompressedSpanPart { - pub(crate) anchor: SpanAnchor, - pub(crate) ctx: SyntaxContext, +struct CompressedSpanPart { + anchor: SpanAnchor, + ctx: SyntaxContext, } impl CompressedSpanPart { @@ -50,376 +42,1086 @@ impl CompressedSpanPart { } } -pub(crate) trait SpanStorage: Copy { - fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool; - - fn new(text_range: TextRange, span_parts_index: usize) -> Self; - - fn text_range(&self) -> TextRange; +trait Encodable: Sized { + #[cfg(all(debug_assertions, not(miri)))] + fn write(self, buffer: &[Cell]); + #[cfg(all(debug_assertions, not(miri)))] + fn read(buffer: &[u8]) -> Self; +} - fn span_parts_index(&self) -> usize; +impl Encodable for u8 { + #[cfg(all(debug_assertions, not(miri)))] + fn write(self, buffer: &[Cell]) { + buffer[0].set(self); + } + #[cfg(all(debug_assertions, not(miri)))] + fn read(buffer: &[u8]) -> Self { + buffer[0] + } +} - #[inline] - fn span(&self, span_parts: &[CompressedSpanPart]) -> Span { - span_parts[self.span_parts_index()].recombine(self.text_range()) +impl Encodable for u16 { + #[cfg(all(debug_assertions, not(miri)))] + fn write(self, buffer: &[Cell]) { + let value = self.to_ne_bytes(); + let buffer: &[Cell; size_of::()] = buffer.try_into().unwrap(); + for (b, v) in std::iter::zip(buffer, value) { + b.set(v); + } + } + #[cfg(all(debug_assertions, not(miri)))] + fn read(buffer: &[u8]) -> Self { + Self::from_ne_bytes(buffer.try_into().unwrap()) } } -#[inline] -const fn n_bits_mask(n: u32) -> u32 { - (1 << n) - 1 +impl Encodable for u32 { + #[cfg(all(debug_assertions, not(miri)))] + fn write(self, buffer: &[Cell]) { + let value = self.to_ne_bytes(); + let buffer: &[Cell; size_of::()] = buffer.try_into().unwrap(); + for (b, v) in std::iter::zip(buffer, value) { + b.set(v); + } + } + #[cfg(all(debug_assertions, not(miri)))] + fn read(buffer: &[u8]) -> Self { + Self::from_ne_bytes(buffer.try_into().unwrap()) + } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct SpanStorage32(u32); +impl Encodable for char { + #[cfg(all(debug_assertions, not(miri)))] + fn write(self, buffer: &[Cell]) { + u32::from(self).write(buffer) + } + #[cfg(all(debug_assertions, not(miri)))] + fn read(buffer: &[u8]) -> Self { + char::from_u32(u32::read(buffer)).unwrap() + } +} -impl SpanStorage32 { - const SPAN_PARTS_BIT: u32 = 4; - const LEN_BITS: u32 = 8; - const OFFSET_BITS: u32 = 20; +struct UninitBuffer { + #[cfg(all(debug_assertions, not(miri)))] + buffer: Box<[u8]>, + #[cfg(not(all(debug_assertions, not(miri))))] + buffer: Box<[MaybeUninit]>, } -const _: () = assert!( - (SpanStorage32::SPAN_PARTS_BIT + SpanStorage32::LEN_BITS + SpanStorage32::OFFSET_BITS) - == u32::BITS -); +impl UninitBuffer { + #[inline] + fn new(capacity: usize) -> Self { + Self { + #[cfg(all(debug_assertions, not(miri)))] + buffer: vec![0; capacity].into_boxed_slice(), + #[cfg(not(all(debug_assertions, not(miri))))] + buffer: Box::new_uninit_slice(capacity), + } + } -impl SpanStorage for SpanStorage32 { #[inline] - fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool { - let offset = u32::from(text_range.start()); - let len = u32::from(text_range.len()); - let span_parts_index = span_parts_index as u32; + fn writer(&mut self) -> BufferWriter<'_> { + BufferWriter { + #[cfg(all(debug_assertions, not(miri)))] + buffer: Cell::from_mut(&mut *self.buffer).as_slice_of_cells(), + #[cfg(not(all(debug_assertions, not(miri))))] + ptr: self.buffer.as_mut_ptr_range().end.cast::(), + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime::new(), + } + } - offset <= n_bits_mask(Self::OFFSET_BITS) - && len <= n_bits_mask(Self::LEN_BITS) - && span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT) + #[cfg(all(debug_assertions, not(miri)))] + unsafe fn finish(self, writer_finish: usize) -> Box<[u8]> { + self.buffer[writer_finish..].into() } + #[cfg(not(all(debug_assertions, not(miri))))] #[inline] - fn new(text_range: TextRange, span_parts_index: usize) -> Self { - let offset = u32::from(text_range.start()); - let len = u32::from(text_range.len()); - let span_parts_index = span_parts_index as u32; - - debug_assert!(offset <= n_bits_mask(Self::OFFSET_BITS)); - debug_assert!(len <= n_bits_mask(Self::LEN_BITS)); - debug_assert!(span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT)); - - Self( - (offset << (Self::LEN_BITS + Self::SPAN_PARTS_BIT)) - | (len << Self::SPAN_PARTS_BIT) - | span_parts_index, - ) + unsafe fn finish(mut self, writer_finish: *mut u8) -> Box<[u8]> { + let end = self.buffer.as_mut_ptr_range().end.cast::(); + unsafe { + let bytes_len = end.offset_from_unsigned(writer_finish); + let mut buffer = Box::<[u8]>::new_uninit_slice(bytes_len); + buffer.as_mut_ptr().cast::().copy_from_nonoverlapping(writer_finish, bytes_len); + buffer.assume_init() + } } +} +#[derive(Clone, Copy)] +struct BufferWriter<'a> { + #[cfg(all(debug_assertions, not(miri)))] + buffer: &'a [Cell], + #[cfg(not(all(debug_assertions, not(miri))))] + ptr: *mut u8, + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime<'a>, +} + +impl<'a> BufferWriter<'a> { #[inline] - fn text_range(&self) -> TextRange { - let offset = TextSize::new(self.0 >> (Self::SPAN_PARTS_BIT + Self::LEN_BITS)); - let len = TextSize::new((self.0 >> Self::SPAN_PARTS_BIT) & n_bits_mask(Self::LEN_BITS)); - TextRange::at(offset, len) + unsafe fn new(buffer: &'a mut [u8]) -> Self { + BufferWriter { + #[cfg(all(debug_assertions, not(miri)))] + buffer: Cell::from_mut(buffer).as_slice_of_cells(), + #[cfg(not(all(debug_assertions, not(miri))))] + ptr: buffer.as_mut_ptr_range().end, + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime::new(), + } + } + + #[cfg_attr(not(all(debug_assertions, not(miri))), inline(always))] + unsafe fn write(&mut self, value: T) { + #[cfg(all(debug_assertions, not(miri)))] + { + let write_at = self.buffer.split_off(self.buffer.len() - size_of::()..).unwrap(); + value.write(write_at); + } + #[cfg(not(all(debug_assertions, not(miri))))] + unsafe { + self.ptr = self.ptr.sub(size_of::()); + self.ptr.cast::().write_unaligned(value); + } } + fn len_since(self, other: BufferWriter<'_>) -> usize { + #[cfg(all(debug_assertions, not(miri)))] + { + other.buffer.len() - self.buffer.len() + } + #[cfg(not(all(debug_assertions, not(miri))))] + { + other.ptr.addr() - self.ptr.addr() + } + } + + #[cfg(all(debug_assertions, not(miri)))] + fn finish(self) -> usize { + self.buffer.len() + } + + #[cfg(not(all(debug_assertions, not(miri))))] #[inline] - fn span_parts_index(&self) -> usize { - (self.0 & n_bits_mask(Self::SPAN_PARTS_BIT)) as usize + fn finish(self) -> *mut u8 { + self.ptr } } -impl fmt::Debug for SpanStorage32 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SpanStorage32") - .field("text_range", &self.text_range()) - .field("span_parts_index", &self.span_parts_index()) - .finish() - } +#[inline] +const fn n_bits_mask(n: u32) -> u32 { + (1 << n) - 1 } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct SpanStorage64 { - offset: u32, - len_and_parts: u32, +#[inline] +const fn n_bits_mask_u64(n: u32) -> u64 { + (1 << n) - 1 } -impl SpanStorage64 { - const SPAN_PARTS_BIT: u32 = 16; - const LEN_BITS: u32 = 16; +// Encoding is done in reverse, from the end to the beginning. This is in order +// to be able to tell how many bytes the children of a `Subtree` occupy, and store +// *that* efficiently, with the smallest possible type. + +#[must_use] +unsafe fn encode_span<'a>( + ptr: BufferWriter<'a>, + span: &Span, + span_parts_map: &FxHashMap, + extra_two_bits: u32, + force_heavy_encoding: bool, +) -> BufferWriter<'a> { + let span_parts_index = span_parts_map[&CompressedSpanPart::from_span(span)] as u32; + let offset = u32::from(span.range.start()); + let len = u32::from(span.range.len()); + unsafe { + encode_span_no_map(ptr, span_parts_index, offset, len, extra_two_bits, force_heavy_encoding) + } } -const _: () = assert!((SpanStorage64::SPAN_PARTS_BIT + SpanStorage64::LEN_BITS) == u32::BITS); +#[must_use] +unsafe fn encode_span_no_map( + mut ptr: BufferWriter<'_>, + mut span_parts_index: u32, + mut offset: u32, + mut len: u32, + extra_two_bits: u32, + force_heavy_encoding: bool, +) -> BufferWriter<'_> { + debug_assert!(extra_two_bits & !0b11 == 0); + let mut first_u32 = extra_two_bits; + first_u32 |= (span_parts_index & n_bits_mask(4)) << 2; + span_parts_index >>= 4; + first_u32 |= (len & n_bits_mask(8)) << (2 + 4); + len >>= 8; + first_u32 |= (offset & n_bits_mask(17)) << (2 + 4 + 8 + 1); + offset >>= 17; + + let extends_to_next = span_parts_index != 0 || len != 0 || offset != 0 || force_heavy_encoding; + first_u32 |= u32::from(extends_to_next) << (2 + 4 + 8); + + if extends_to_next { + ptr = unsafe { + encode_extended_span(ptr, span_parts_index, len, offset, force_heavy_encoding) + }; + } + unsafe { ptr.write::(first_u32) }; -impl SpanStorage for SpanStorage64 { - #[inline] - fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool { - let len = u32::from(text_range.len()); - let span_parts_index = span_parts_index as u32; + ptr +} - len <= n_bits_mask(Self::LEN_BITS) && span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT) +#[cold] +#[must_use] +unsafe fn encode_extended_span( + mut ptr: BufferWriter<'_>, + span_parts_index: u32, + len: u32, + offset: u32, + force_heavy_encoding: bool, +) -> BufferWriter<'_> { + if span_parts_index <= n_bits_mask(11) + && len <= n_bits_mask(10) + && offset <= n_bits_mask(10) + && !force_heavy_encoding + { + let mut second_u32 = span_parts_index; + second_u32 |= len << 11; + second_u32 |= offset << (11 + 10); + second_u32 <<= 1; + unsafe { ptr.write::(second_u32) }; + } else { + assert!(span_parts_index <= n_bits_mask(24), "too big `span_parts_index`"); + + let mut u64 = u64::from(span_parts_index); + u64 |= u64::from(len) << 24; + u64 |= u64::from(offset) << (24 + 24); + let third_u32 = u64 as u32; + let mut second_u32 = (u64 >> u32::BITS) as u32; + second_u32 <<= 1; + second_u32 |= 0b1; + unsafe { + ptr.write::(third_u32); + ptr.write::(second_u32); + } } - #[inline] - fn new(text_range: TextRange, span_parts_index: usize) -> Self { - let offset = u32::from(text_range.start()); - let len = u32::from(text_range.len()); - let span_parts_index = span_parts_index as u32; + ptr +} + +#[must_use] +unsafe fn encode_symbol<'a>( + mut ptr: BufferWriter<'a>, + symbol: &Symbol, + tag: u32, + symbols_map: &FxHashMap, +) -> BufferWriter<'a> { + let symbol_idx = symbols_map[symbol] as u32; + unsafe { + if symbol_idx <= n_bits_mask(6) { + ptr.write::(((symbol_idx << 2) | tag) as u8); + } else if symbol_idx <= n_bits_mask(13) { + ptr.write::(((symbol_idx >> 6) << 1) as u8); + ptr.write::(((symbol_idx << 2) | 0b10 | tag) as u8); + } else { + ptr.write::((symbol_idx >> 13) as u16); + ptr.write::((((symbol_idx >> 6) << 1) | 0b1) as u8); + ptr.write::(((symbol_idx << 2) | 0b10 | tag) as u8); + } + } + ptr +} - debug_assert!(len <= n_bits_mask(Self::LEN_BITS)); - debug_assert!(span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT)); +#[must_use] +unsafe fn encode<'a>( + mut ptr: BufferWriter<'a>, + tt: TokenTree, + span_parts_map: &FxHashMap, + byte_size_after: &mut [u32], + symbols_map: &FxHashMap, +) -> BufferWriter<'a> { + let before_ptr = ptr; + unsafe { + match tt { + TokenTree::Leaf(Leaf::Punct(Punct { char, spacing, span })) => { + if char.is_ascii() { + let spacing = spacing as u8; + let span_extra = 0b1 | (u32::from(spacing) & 0b10); + let char = ((char as u8) << 1) | (spacing & 0b1); + ptr.write::(char); + ptr = encode_span(ptr, &span, span_parts_map, span_extra, false); + } else { + let mut control_byte = 0b110; + control_byte |= (spacing as u8) << 3; + ptr.write::(char); + ptr.write::(control_byte); + ptr = encode_span(ptr, &span, span_parts_map, 0b00, false); + } + } + TokenTree::Leaf(Leaf::Ident(Ident { sym, span, is_raw })) => { + ptr = encode_symbol(ptr, &sym, is_raw as u32, symbols_map); + ptr = encode_span(ptr, &span, span_parts_map, 0b10, false); + } + TokenTree::Leaf(Leaf::Literal(Literal { text_and_suffix, span, kind, suffix_len })) => { + if matches!(kind, LitKind::Str | LitKind::StrRaw(0 | 1) | LitKind::Integer) + && u32::from(suffix_len) <= n_bits_mask(4) + { + // Literal, format 1. + let mut control_byte = match kind { + LitKind::Str => 0b0_011, + LitKind::StrRaw(0) => 0b0_100, + LitKind::StrRaw(1) => 0b1_011, + LitKind::Integer => 0b1_100, + _ => unreachable!(), + }; + control_byte |= suffix_len << 4; + ptr = encode_symbol(ptr, &text_and_suffix, 0, symbols_map); + ptr.write::(control_byte); + } else { + // Literal, format 2. + let mut control_byte = 0b111; + let (kind, raw_count) = match kind { + LitKind::Byte => (0, None), + LitKind::Char => (1, None), + LitKind::Integer => (2, None), + LitKind::Float => (3, None), + LitKind::Str => (4, None), + LitKind::StrRaw(count) => (5, Some(count)), + LitKind::ByteStr => (6, None), + LitKind::ByteStrRaw(count) => (7, Some(count)), + LitKind::CStr => (8, None), + LitKind::CStrRaw(count) => (9, Some(count)), + LitKind::Err(()) => (10, None), + }; + control_byte |= kind << 3; + ptr = encode_symbol(ptr, &text_and_suffix, 0, symbols_map); + ptr.write::(suffix_len); + if let Some(raw_count) = raw_count { + ptr.write::(raw_count); + } + ptr.write::(control_byte); + } + ptr = encode_span(ptr, &span, span_parts_map, 0b00, false); + } + TokenTree::Subtree(Subtree { delimiter, len }) => { + let open_span_parts_index = + span_parts_map[&CompressedSpanPart::from_span(&delimiter.open)] as u32; + let close_span_parts_index = + span_parts_map[&CompressedSpanPart::from_span(&delimiter.close)] as u32; + let close_span_offset_from_open = delimiter + .close + .range + .start() + .checked_sub(delimiter.open.range.start()) + .map_or(u32::MAX, u32::from); + let children_byte_len = byte_size_after[1] - byte_size_after[1 + len as usize]; + if open_span_parts_index == close_span_parts_index + && len <= n_bits_mask(u8::BITS) + && children_byte_len <= n_bits_mask(u8::BITS) + && delimiter.open.range.len() == TextSize::new(1) + && delimiter.close.range.len() == TextSize::new(1) + && close_span_offset_from_open <= n_bits_mask(11) + { + // Subtree, format 1. + let span = Span { + range: TextRange::at( + delimiter.open.range.start(), + TextSize::new(close_span_offset_from_open), + ), + anchor: delimiter.open.anchor, + ctx: delimiter.open.ctx, + }; + let mut control_byte = 0b000; + control_byte |= (delimiter.kind as u8) << 3; + control_byte |= ((close_span_offset_from_open >> 8) << (3 + 2)) as u8; + ptr.write::(children_byte_len as u8); + ptr.write::(len as u8); + ptr.write::(control_byte); + ptr = encode_span(ptr, &span, span_parts_map, 0b00, false); + } else if open_span_parts_index == close_span_parts_index + && len <= n_bits_mask(u8::BITS) + && children_byte_len <= n_bits_mask(u8::BITS) + && delimiter.open.range.len() == delimiter.close.range.len() + && close_span_offset_from_open <= n_bits_mask(3) + { + // Subtree, format 2. + let mut control_byte = 0b001; + control_byte |= (delimiter.kind as u8) << 3; + control_byte |= (close_span_offset_from_open << (3 + 2)) as u8; + ptr.write::(children_byte_len as u8); + ptr.write::(len as u8); + ptr.write::(control_byte); + ptr = encode_span(ptr, &delimiter.open, span_parts_map, 0b00, false); + } else if len <= n_bits_mask(u8::BITS) + && children_byte_len <= n_bits_mask(12) + && delimiter.open.range.len() == delimiter.close.range.len() + && close_span_offset_from_open <= n_bits_mask(8) + && close_span_parts_index <= n_bits_mask(7) + { + // Subtree, format 3. + let mut control_byte = 0b010; + control_byte |= (delimiter.kind as u8) << 3; + control_byte |= (close_span_parts_index << (3 + 2)) as u8; + let mut children_byte_len = children_byte_len << 4; + children_byte_len |= close_span_parts_index >> 3; + ptr.write::(children_byte_len as u16); + ptr.write::(len as u8); + ptr.write::(close_span_offset_from_open as u8); + ptr.write::(control_byte); + ptr = encode_span(ptr, &delimiter.open, span_parts_map, 0b00, false); + } else { + // Subtree, format 4. + let mut control_byte = 0b101; + control_byte |= (delimiter.kind as u8) << 3; + ptr.write::(children_byte_len); + ptr.write::(len); + ptr = encode_span(ptr, &delimiter.close, span_parts_map, 0b00, false); + ptr.write::(control_byte); + ptr = encode_span(ptr, &delimiter.open, span_parts_map, 0b00, false); + } + } + } - Self { offset, len_and_parts: (len << Self::SPAN_PARTS_BIT) | span_parts_index } + let element_byte_size: u32 = ptr.len_since(before_ptr).try_into().unwrap(); + byte_size_after[0] = byte_size_after[1] + element_byte_size; } - #[inline] - fn text_range(&self) -> TextRange { - let offset = TextSize::new(self.offset); - let len = TextSize::new(self.len_and_parts >> Self::SPAN_PARTS_BIT); - TextRange::at(offset, len) + ptr +} + +/// We always encode the top subtree with the heaviest encoding because we sometimes want to change it. +unsafe fn encode_top_subtree<'a>( + mut ptr: BufferWriter<'a>, + top_subtree: Subtree, + open_span_parts_index: u32, + byte_size_after: &[u32], +) -> BufferWriter<'a> { + unsafe { + let Subtree { delimiter, len } = top_subtree; + let children_byte_len = byte_size_after[1] - byte_size_after[1 + len as usize]; + + let mut control_byte = 0b101; + control_byte |= (delimiter.kind as u8) << 3; + ptr.write::(children_byte_len); + ptr.write::(len); + ptr = encode_span_no_map( + ptr, + open_span_parts_index + 1, + delimiter.close.range.start().into(), + delimiter.close.range.len().into(), + 0b00, + true, + ); + ptr.write::(control_byte); + ptr = encode_span_no_map( + ptr, + open_span_parts_index, + delimiter.open.range.start().into(), + delimiter.open.range.len().into(), + 0b00, + true, + ); } + ptr +} - #[inline] - fn span_parts_index(&self) -> usize { - (self.len_and_parts & n_bits_mask(Self::SPAN_PARTS_BIT)) as usize +fn change_root_delimiter(buffer: &mut [u8], new_delim: DelimiterKind) { + // The span is 3*u32 and then the control byte, in which the delimiter comes. + let control_byte_index = 3 * size_of::(); + let mut control_byte = buffer[control_byte_index]; + control_byte &= 0b111; // Remove previous delimiter. + control_byte |= (new_delim as u8) << 3; + buffer[control_byte_index] = control_byte; +} + +unsafe fn change_root_spans( + buffer: &mut [u8], + open_span_parts_index: u32, + close_span_parts_index: u32, + open_range: TextRange, + close_range: TextRange, +) { + // Remember we write in reverse, so we add `3 * size_of::()`. + unsafe { + _ = encode_span_no_map( + BufferWriter::new(&mut buffer[..3 * size_of::()]), + open_span_parts_index, + u32::from(open_range.start()), + u32::from(open_range.len()), + 0b00, + true, + ); + _ = encode_span_no_map( + BufferWriter::new( + &mut buffer[..3 * size_of::() + size_of::() + 3 * size_of::()], + ), + close_span_parts_index, + u32::from(close_range.start()), + u32::from(close_range.len()), + 0b00, + true, + ); } } -impl fmt::Debug for SpanStorage64 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SpanStorage64") - .field("text_range", &self.text_range()) - .field("span_parts_index", &self.span_parts_index()) - .finish() +/// This is subtree in format 4: two spans, each at most 3*u32, a u8 control byte, a u32 length and a u32 bytes length. +const BIGGEST_POSSIBLE_TT_ENCODING: usize = + 2 * 3 * size_of::() + size_of::() + size_of::() + size_of::(); + +fn encode_all( + tts: std::vec::IntoIter, + mut compressed_span_frequencies: FxHashMap, + mut symbol_frequencies: FxHashMap, +) -> TopSubtree { + let tts_len = tts.len(); + let mut token_trees = tts.enumerate(); + let Some((_, TokenTree::Subtree(top_subtree))) = token_trees.next() else { + panic!("must always have a top subtree"); + }; + + let (span_parts, span_parts_map) = { + let mut compressed_spans = compressed_span_frequencies + .keys() + .copied() + .chain([ + CompressedSpanPart::from_span(&top_subtree.delimiter.open), + CompressedSpanPart::from_span(&top_subtree.delimiter.close), + ]) + .collect::>(); + { + // For this purpose, do not consider the top delimiters. They should stay last and not affect the other spans, + // since we might want to change them. + let len = compressed_spans.len(); + let compressed_spans = &mut compressed_spans[..len - 2]; + // No need sort if there is already enough space for everyone to be encoded efficiently. + if compressed_span_frequencies.len() > n_bits_mask(4) as usize { + // We want more used spans to have lower indices, so they can be encoded more efficiently. + compressed_spans.sort_unstable_by_key(|span| { + std::cmp::Reverse(compressed_span_frequencies[span]) + }); + } + for (index, span) in compressed_spans.iter().enumerate() { + *compressed_span_frequencies.get_mut(span).unwrap() = index; + } + } + (compressed_spans, compressed_span_frequencies) + }; + + let (symbols, symbols_map) = { + let mut symbols = symbol_frequencies.keys().cloned().collect::>(); + // No need sort if there is already enough space for everyone to be encoded efficiently. + if symbol_frequencies.len() > n_bits_mask(6) as usize { + // We want more used spans to have lower indices, so they can be encoded more efficiently. + symbols.sort_unstable_by_key(|symbol| std::cmp::Reverse(symbol_frequencies[symbol])); + } + for (index, span) in symbols.iter().enumerate() { + *symbol_frequencies.get_mut(span).unwrap() = index; + } + (symbols, symbol_frequencies) + }; + + // +1 because each `encode()` calls reads the previous value. + let mut byte_size_after = vec![0u32; tts_len + 1]; + let bytes_capacity = tts_len * BIGGEST_POSSIBLE_TT_ENCODING; + unsafe { + let mut temp_buffer = UninitBuffer::new(bytes_capacity); + let mut ptr = temp_buffer.writer(); + for (index, tt) in token_trees.rev() { + ptr = encode(ptr, tt, &span_parts_map, &mut byte_size_after[index..], &symbols_map); + } + ptr = encode_top_subtree(ptr, top_subtree, (span_parts.len() - 2) as u32, &byte_size_after); + + let writer_finish = ptr.finish(); + let buffer = temp_buffer.finish(writer_finish); + + TopSubtree { buffer, span_parts, len: tts_len, symbols } } } -impl From for SpanStorage64 { - #[inline] - fn from(value: SpanStorage32) -> Self { - SpanStorage64::new(value.text_range(), value.span_parts_index()) +fn compute_span_frequencies_and_symbols( + tts: &[TokenTree], +) -> (FxHashMap, FxHashMap) { + let mut span_frequencies = FxHashMap::default(); + let mut symbols = FxHashMap::default(); + for tt in tts { + match tt { + TokenTree::Leaf(leaf) => { + if let Some(symbol) = leaf.symbol() { + *symbols.entry(symbol.clone()).or_insert(0) += 1; + } + + *span_frequencies.entry(CompressedSpanPart::from_span(leaf.span())).or_insert(0) += + 1; + } + TokenTree::Subtree(subtree) => { + *span_frequencies + .entry(CompressedSpanPart::from_span(&subtree.delimiter.open)) + .or_insert(0) += 1; + *span_frequencies + .entry(CompressedSpanPart::from_span(&subtree.delimiter.close)) + .or_insert(0) += 1; + } + } } + (span_frequencies, symbols) } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct SpanStorage96 { - offset: u32, - len: u32, - parts: u32, +#[derive(Clone, Copy)] +struct BufferReader<'a> { + #[cfg(all(debug_assertions, not(miri)))] + buffer: &'a [u8], + #[cfg(not(all(debug_assertions, not(miri))))] + ptr: *const u8, + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime<'a>, } -impl SpanStorage for SpanStorage96 { +impl<'a> BufferReader<'a> { #[inline] - fn can_hold(_text_range: TextRange, _span_parts_index: usize) -> bool { - true + fn start_end(slice: &'a [u8]) -> (Self, Self) { + #[cfg(all(debug_assertions, not(miri)))] + { + let start = Self { buffer: slice }; + let end = Self { buffer: &slice[slice.len()..] }; + (start, end) + } + #[cfg(not(all(debug_assertions, not(miri))))] + { + let ptrs = slice.as_ptr_range(); + let start = Self { + ptr: ptrs.start, + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime::new(), + }; + let end = Self { + ptr: ptrs.end, + #[cfg(not(all(debug_assertions, not(miri))))] + _marker: stdx::variance::PhantomCovariantLifetime::new(), + }; + (start, end) + } } - #[inline] - fn new(text_range: TextRange, span_parts_index: usize) -> Self { - let offset = u32::from(text_range.start()); - let len = u32::from(text_range.len()); - let span_parts_index = span_parts_index as u32; - - Self { offset, len, parts: span_parts_index } + #[cfg_attr(not(all(debug_assertions, not(miri))), inline(always))] + unsafe fn read(&mut self) -> T { + #[cfg(all(debug_assertions, not(miri)))] + { + let read_at = self.buffer.split_off(..size_of::()).unwrap(); + T::read(read_at) + } + #[cfg(not(all(debug_assertions, not(miri))))] + unsafe { + let result = self.ptr.cast::().read_unaligned(); + self.ptr = self.ptr.add(size_of::()); + result + } } - #[inline] - fn text_range(&self) -> TextRange { - let offset = TextSize::new(self.offset); - let len = TextSize::new(self.len); - TextRange::at(offset, len) + #[cfg_attr(not(all(debug_assertions, not(miri))), inline(always))] + unsafe fn skip(&mut self, amount: usize) { + #[cfg(all(debug_assertions, not(miri)))] + { + self.buffer = &self.buffer[amount..]; + } + #[cfg(not(all(debug_assertions, not(miri))))] + unsafe { + self.ptr = self.ptr.add(amount); + } } +} +impl PartialEq for BufferReader<'_> { #[inline] - fn span_parts_index(&self) -> usize { - self.parts as usize + fn eq(&self, other: &Self) -> bool { + #[cfg(all(debug_assertions, not(miri)))] + let self_ptr = self.buffer.as_ptr(); + #[cfg(not(all(debug_assertions, not(miri))))] + let self_ptr = self.ptr; + #[cfg(all(debug_assertions, not(miri)))] + let other_ptr = other.buffer.as_ptr(); + #[cfg(not(all(debug_assertions, not(miri))))] + let other_ptr = other.ptr; + + self_ptr == other_ptr } } -impl fmt::Debug for SpanStorage96 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SpanStorage96") - .field("text_range", &self.text_range()) - .field("span_parts_index", &self.span_parts_index()) - .finish() - } +struct InlineSpanParts { + span_parts_index: usize, + text_range: TextRange, } -impl From for SpanStorage96 { - #[inline] - fn from(value: SpanStorage32) -> Self { - SpanStorage96::new(value.text_range(), value.span_parts_index()) +#[inline] +unsafe fn decode_span( + ptr: BufferReader<'_>, + mut first_u32: u32, +) -> (BufferReader<'_>, InlineSpanParts) { + first_u32 >>= 2; + let span_parts_index = first_u32 & n_bits_mask(4); + let len = (first_u32 >> 4) & n_bits_mask(8); + let extends_to_next = (first_u32 & (1 << (4 + 8))) != 0; + let offset = first_u32 >> (4 + 8 + 1); + if !extends_to_next { + let result = InlineSpanParts { + span_parts_index: span_parts_index as usize, + text_range: TextRange::at(TextSize::new(offset), TextSize::new(len)), + }; + (ptr, result) + } else { + unsafe { decode_extended_span(ptr, span_parts_index, len, offset) } } } -impl From for SpanStorage96 { - #[inline] - fn from(value: SpanStorage64) -> Self { - SpanStorage96::new(value.text_range(), value.span_parts_index()) +#[cold] +unsafe fn decode_extended_span( + mut ptr: BufferReader<'_>, + mut span_parts_index: u32, + mut len: u32, + mut offset: u32, +) -> (BufferReader<'_>, InlineSpanParts) { + unsafe { + let mut second_u32 = ptr.read::(); + let extends_to_next = (second_u32 & 0b1) != 0; + second_u32 >>= 1; + if extends_to_next { + let third_u32 = ptr.read::(); + let u64 = u64::from(third_u32) | (u64::from(second_u32) << u32::BITS); + let rest_span_parts_index = (u64 & n_bits_mask_u64(24)) as u32; + span_parts_index |= rest_span_parts_index << 4; + let rest_len = ((u64 >> 24) & n_bits_mask_u64(24)) as u32; + len |= rest_len << 8; + let rest_offset = (u64 >> (24 + 24)) as u32; + offset |= rest_offset << 17; + } else { + let rest_span_parts_index = second_u32 & n_bits_mask(11); + span_parts_index |= rest_span_parts_index << 4; + let rest_len = (second_u32 >> 11) & n_bits_mask(10); + len |= rest_len << 8; + let rest_offset = second_u32 >> (11 + 10); + offset |= rest_offset << 17; + }; + let result = InlineSpanParts { + span_parts_index: span_parts_index as usize, + text_range: TextRange::at(TextSize::new(offset), TextSize::new(len)), + }; + (ptr, result) } } -// We don't use structs or enum nesting here to save padding. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum TokenTree { - Literal { text_and_suffix: Symbol, span: S, kind: LitKind, suffix_len: u8 }, - Punct { char: char, spacing: Spacing, span: S }, - Ident { sym: Symbol, span: S, is_raw: IdentIsRaw }, - Subtree { len: u32, delim_kind: DelimiterKind, open_span: S, close_span: S }, +// FIXME: It'll probably be better to ensure this ourselves via a `#[repr(C, align(4))]` wrapper, even though practically +// this holds for all 32- and 64-bit targets (Rust does not guarantee this). +const _: () = assert!(align_of::<*const *const str>() >= 4); // Needed for the tagging of idents. + +unsafe fn decode_symbol<'a>( + mut ptr: BufferReader<'a>, + first_byte: u8, + symbols: &[Symbol], +) -> (BufferReader<'a>, Symbol) { + let mut symbol_idx = u32::from(first_byte) >> 1; + let extends_to_next = symbol_idx & 0b1 == 0b1; + symbol_idx >>= 1; + if extends_to_next { + let mut second_byte = unsafe { ptr.read::() }; + let rest_bytes = + if second_byte & 0b1 == 0b1 { u32::from(unsafe { ptr.read::() }) } else { 0 }; + second_byte >>= 1; + symbol_idx |= u32::from(second_byte) << 6; + symbol_idx |= rest_bytes << 13; + } + (ptr, symbols[symbol_idx as usize].clone()) } -impl TokenTree { - #[inline] - pub(crate) fn first_span(&self) -> &S { - match self { - TokenTree::Literal { span, .. } => span, - TokenTree::Punct { span, .. } => span, - TokenTree::Ident { span, .. } => span, - TokenTree::Subtree { open_span, .. } => open_span, +/// We need `MaybeUninit` to preserve provenance. +/// +/// The returned `u32` is the length of the children *in bytes*, if we read a subtree. Otherwise it's zero. +unsafe fn decode<'a>( + mut ptr: BufferReader<'a>, + compressed: &[CompressedSpanPart], + symbols: &[Symbol], +) -> (BufferReader<'a>, TokenTree, u32) { + unsafe { + let span_and_extra = ptr.read::(); + let span; + (ptr, span) = decode_span(ptr, span_and_extra); + let span = compressed[span.span_parts_index].recombine(span.text_range); + + if span_and_extra & 0b1 == 0b1 { + // An ASCII punct. + let char_and_half_spacing = ptr.read::(); + + let spacing = (span_and_extra & 0b10) | (u32::from(char_and_half_spacing) & 0b1); + let spacing = transmute::(spacing as u8); + + let char = char::from(char_and_half_spacing >> 1); + + return (ptr, TokenTree::Leaf(Leaf::Punct(Punct { char, spacing, span })), 0); + } else if span_and_extra & 0b10 == 0b10 { + // An ident. + let symbol_first_byte = ptr.read::(); + let is_raw = symbol_first_byte & 0b1; + let symbol; + (ptr, symbol) = decode_symbol(ptr, symbol_first_byte, symbols); + let is_raw = transmute::(is_raw); + + return (ptr, TokenTree::Leaf(Leaf::Ident(Ident { sym: symbol, span, is_raw })), 0); } - } - #[inline] - pub(crate) fn last_span(&self) -> &S { - match self { - TokenTree::Literal { span, .. } => span, - TokenTree::Punct { span, .. } => span, - TokenTree::Ident { span, .. } => span, - TokenTree::Subtree { close_span, .. } => close_span, - } - } + let mut children_byte_len = 0; + + let control_byte = u32::from(ptr.read::()); + let control_byte_extra_data = control_byte >> 3; + let result = match control_byte & 0b111 { + 0b000 => { + // Subtree, format 1: + // - Same span_parts_index for open and close span. + // - Subtree length is a u8. + // - Subtree length in bytes is a u8. + // - The length of both the open and close span is 1 - so we use the already-parsed length for the open span + // for other things (we can only assume it has 8 bits available, the minimum format for a length). + // - The offset between the open span's start and the close span's start is stored in 11 bits. + let kind = transmute::((control_byte_extra_data & 0b11) as u8); + let mut open_span = span; + let mut close_span_offset_from_open = u32::from(span.range.len()); + open_span.range = TextRange::at(open_span.range.start(), TextSize::new(1)); + close_span_offset_from_open |= (control_byte_extra_data >> 2) << 8; + let close_span = Span { + range: open_span.range + TextSize::new(close_span_offset_from_open), + anchor: open_span.anchor, + ctx: open_span.ctx, + }; + let len = u32::from(ptr.read::()); + children_byte_len = u32::from(ptr.read::()); - #[inline] - pub(crate) fn to_api(&self, span_parts: &[CompressedSpanPart]) -> crate::TokenTree { - match self { - TokenTree::Literal { text_and_suffix, span, kind, suffix_len } => { - crate::TokenTree::Leaf(crate::Leaf::Literal(crate::Literal { - text_and_suffix: text_and_suffix.clone(), - span: span.span(span_parts), - kind: *kind, - suffix_len: *suffix_len, - })) + TokenTree::Subtree(Subtree { + delimiter: Delimiter { open: open_span, close: close_span, kind }, + len, + }) } - TokenTree::Punct { char, spacing, span } => { - crate::TokenTree::Leaf(crate::Leaf::Punct(crate::Punct { - char: *char, - spacing: *spacing, - span: span.span(span_parts), - })) + 0b001 => { + // Subtree, format 2: + // - Same span_parts_index for open and close span. + // - Subtree length is a u8. + // - Subtree length in bytes is a u8. + // - The open and close span have the same length. This covers many cases because most cases either give + // both length 1 (the brackets themselves) or the same span (usually, the whole range they encompass). + // - The offset between the open span's start and the close span's start is stored in 3 bits. + let kind = transmute::((control_byte_extra_data & 0b11) as u8); + let open_span = span; + let close_span_offset_from_open = control_byte_extra_data >> 2; + let close_span = Span { + range: open_span.range + TextSize::new(close_span_offset_from_open), + anchor: open_span.anchor, + ctx: open_span.ctx, + }; + let len = u32::from(ptr.read::()); + children_byte_len = u32::from(ptr.read::()); + + TokenTree::Subtree(Subtree { + delimiter: Delimiter { open: open_span, close: close_span, kind }, + len, + }) } - TokenTree::Ident { sym, span, is_raw } => { - crate::TokenTree::Leaf(crate::Leaf::Ident(crate::Ident { - sym: sym.clone(), - span: span.span(span_parts), - is_raw: *is_raw, - })) + 0b010 => { + // Subtree, format 3: + // - Subtree length is a u8. + // - Subtree length in bytes is 12 bits. + // - The open and close span have the same length. + // - The offset between the open span's start and the close span's start is stored in 8 bits. + // - The close span's span_parts_index is stored in 7 bits. + // This is less efficient than formats 1 and 2 (requires two more bytes), but more efficient than the general format. + let kind = transmute::((control_byte_extra_data & 0b11) as u8); + let open_span = span; + let close_span_offset_from_open = u32::from(ptr.read::()); + let len = u32::from(ptr.read::()); + children_byte_len = u32::from(ptr.read::()); + let mut close_span_parts_index = control_byte_extra_data >> 2; + close_span_parts_index |= (children_byte_len & 0b1111) << 3; + children_byte_len >>= 4; + let close_span = compressed[close_span_parts_index as usize] + .recombine(open_span.range + TextSize::new(close_span_offset_from_open)); + + TokenTree::Subtree(Subtree { + delimiter: Delimiter { open: open_span, close: close_span, kind }, + len, + }) } - TokenTree::Subtree { len, delim_kind, open_span, close_span } => { - crate::TokenTree::Subtree(crate::Subtree { - delimiter: crate::Delimiter { - open: open_span.span(span_parts), - close: close_span.span(span_parts), - kind: *delim_kind, - }, - len: *len, + 0b101 => { + cold_path(); + + // Subtree, format 4 - most general format: subtree length is u32, subtree length in bytes is u32, full decoded + // span for close span, delimiter kind is 5 bits (not needed now, will be needed when we have different kinds of + // invisible delimiters). + let kind = transmute::(control_byte_extra_data as u8); + let open_span = span; + let close_span_start = ptr.read::(); + let close_span; + (ptr, close_span) = decode_span(ptr, close_span_start); + let close_span = + compressed[close_span.span_parts_index].recombine(close_span.text_range); + let len = ptr.read::(); + children_byte_len = ptr.read::(); + + TokenTree::Subtree(Subtree { + delimiter: Delimiter { open: open_span, close: close_span, kind }, + len, }) } - } - } + 0b110 => { + cold_path(); - #[inline] - fn convert>(self) -> TokenTree { - match self { - TokenTree::Literal { text_and_suffix, span, kind, suffix_len } => { - TokenTree::Literal { text_and_suffix, span: span.into(), kind, suffix_len } + // Non-ASCII punct. Extremely rare but technically possible. + let spacing = transmute::(control_byte_extra_data as u8); + let char = ptr.read::(); + + TokenTree::Leaf(Leaf::Punct(Punct { char, spacing, span })) } - TokenTree::Punct { char, spacing, span } => { - TokenTree::Punct { char, spacing, span: span.into() } + 0b011 | 0b100 => { + // Literal, format 1: the 6 bits remaining from `control_byte` decide the kind and the suffix len from a constant set + // (of the most common). + let control_byte_extra_data = control_byte >> 2; + let text_and_suffix_first_byte = ptr.read::(); + let text_and_suffix; + (ptr, text_and_suffix) = decode_symbol(ptr, text_and_suffix_first_byte, symbols); + let kind = match control_byte_extra_data & 0b11 { + 0b00 => LitKind::Str, + 0b01 => LitKind::StrRaw(0), + 0b10 => LitKind::StrRaw(1), + 0b11 => LitKind::Integer, + _ => unreachable!(), + }; + let suffix_len = (control_byte_extra_data >> 2) as u8; + + TokenTree::Leaf(Leaf::Literal(Literal { text_and_suffix, span, kind, suffix_len })) } - TokenTree::Ident { sym, span, is_raw } => { - TokenTree::Ident { sym, span: span.into(), is_raw } + 0b111 => { + cold_path(); + + // Literal, format 2: most general format. + let kind = match control_byte_extra_data { + 0 => LitKind::Byte, + 1 => LitKind::Char, + 2 => LitKind::Integer, + 3 => LitKind::Float, + 4 => LitKind::Str, + 5 => LitKind::StrRaw(ptr.read::()), + 6 => LitKind::ByteStr, + 7 => LitKind::ByteStrRaw(ptr.read::()), + 8 => LitKind::CStr, + 9 => LitKind::CStrRaw(ptr.read::()), + 10 => LitKind::Err(()), + _ => unreachable!(), + }; + let suffix_len = ptr.read::(); + let text_and_suffix_first_byte = ptr.read::(); + let text_and_suffix; + (ptr, text_and_suffix) = decode_symbol(ptr, text_and_suffix_first_byte, symbols); + + TokenTree::Leaf(Leaf::Literal(Literal { text_and_suffix, span, kind, suffix_len })) } - TokenTree::Subtree { len, delim_kind, open_span, close_span } => TokenTree::Subtree { - len, - delim_kind, - open_span: open_span.into(), - close_span: close_span.into(), - }, - } + _ => unreachable!(), + }; + (ptr, result, children_byte_len) } } -// This is used a lot, make sure it doesn't grow unintentionally. -const _: () = { - assert!(size_of::>() == 16); - assert!(size_of::>() == 24); - assert!(size_of::>() == 32); -}; +#[derive(Clone, Copy)] +pub(crate) struct TokenTreesSlice<'a> { + current: BufferReader<'a>, + end: BufferReader<'a>, + span_parts: &'a [CompressedSpanPart], + symbols: &'a [Symbol], +} -#[rust_analyzer::macro_style(braces)] -macro_rules! dispatch { - ( - match $scrutinee:expr => $tt:ident => $body:expr - ) => { - match $scrutinee { - TopSubtreeRepr::SpanStorage32($tt) => $body, - TopSubtreeRepr::SpanStorage64($tt) => $body, - TopSubtreeRepr::SpanStorage96($tt) => $body, +unsafe impl Send for TokenTreesSlice<'_> {} +unsafe impl Sync for TokenTreesSlice<'_> {} + +impl<'a> TokenTreesSlice<'a> { + #[inline] + fn new(top_subtree: &'a TopSubtree) -> Self { + let (current, end) = BufferReader::start_end(&top_subtree.buffer); + Self { current, end, span_parts: &top_subtree.span_parts, symbols: &top_subtree.symbols } + } + + #[inline] + pub(crate) fn empty() -> Self { + let (current, end) = BufferReader::start_end(&[]); + Self { current, end, span_parts: &[], symbols: &[] } + } + + pub(crate) fn advance(&mut self) -> Option { + if self.current == self.end { + return None; } - }; -} -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum TopSubtreeRepr { - SpanStorage32(Box<[TokenTree]>), - SpanStorage64(Box<[TokenTree]>), - SpanStorage96(Box<[TokenTree]>), + let (new_current, token_tree, _children_byte_len) = + unsafe { decode(self.current, self.span_parts, self.symbols) }; + self.current = new_current; + Some(token_tree) + } + + /// This is like `advance()`, but when encountering a subtree, it changes `self` to skip it and returns a + /// slice into it (what `advance()` would have done to `self`). + pub(crate) fn advance_skip_subtree(&mut self) -> Option<(TokenTree, TokenTreesSlice<'a>)> { + if self.current == self.end { + return None; + } + + let (new_current, token_tree, children_byte_len) = + unsafe { decode(self.current, self.span_parts, self.symbols) }; + self.current = new_current; + let subtree_slice = *self; + unsafe { self.current.skip(children_byte_len as usize) }; + Some((token_tree, subtree_slice)) + } + + pub(crate) fn iter(mut self) -> impl Iterator { + std::iter::from_fn(move || self.advance()) + } } #[derive(Clone, PartialEq, Eq, Hash)] pub struct TopSubtree { - repr: TopSubtreeRepr, + buffer: Box<[u8]>, + /// The last two are the top subtree's open and close span, in this order. span_parts: Box<[CompressedSpanPart]>, + symbols: Box<[Symbol]>, + len: usize, } impl TopSubtree { pub fn empty(span: DelimSpan) -> Self { - Self { - repr: TopSubtreeRepr::SpanStorage96(Box::new([TokenTree::Subtree { + encode_all( + vec![TokenTree::Subtree(Subtree { + delimiter: Delimiter::invisible_delim_spanned(span), len: 0, - delim_kind: DelimiterKind::Invisible, - open_span: SpanStorage96::new(span.open.range, 0), - close_span: SpanStorage96::new(span.close.range, 1), - }])), - span_parts: Box::new([ - CompressedSpanPart::from_span(&span.open), - CompressedSpanPart::from_span(&span.close), - ]), - } - } - - pub fn invisible_from_leaves( - delim_span: Span, - leaves: [crate::Leaf; N], - ) -> Self { - let mut builder = TopSubtreeBuilder::new(crate::Delimiter::invisible_spanned(delim_span)); - builder.extend(leaves); - builder.build() + })] + .into_iter(), + FxHashMap::default(), + FxHashMap::default(), + ) } - pub fn from_token_trees(delimiter: crate::Delimiter, token_trees: TokenTreesView<'_>) -> Self { + pub fn invisible_from_leaves(delim_span: Span, leaves: [Leaf; N]) -> Self { + Self::from_serialized( + std::iter::chain( + [TokenTree::Subtree(Subtree { + delimiter: Delimiter::invisible_spanned(delim_span), + len: leaves.len() as u32, + })], + leaves.into_iter().map(TokenTree::Leaf), + ) + .collect(), + ) + } + + pub fn from_token_trees(delimiter: Delimiter, token_trees: TokenTreesView<'_>) -> Self { let mut builder = TopSubtreeBuilder::new(delimiter); builder.extend_with_tt(token_trees); builder.build() } - pub fn from_serialized(tt: Vec) -> Self { - let mut tt = tt.into_iter(); - let Some(crate::TokenTree::Subtree(top_subtree)) = tt.next() else { - panic!("first must always come the top subtree") - }; - let mut builder = TopSubtreeBuilder::new(top_subtree.delimiter); - for tt in tt { - builder.push_token_tree(tt); - } - builder.build() + pub fn from_serialized(tts: Vec) -> Self { + let (span_frequencies, symbols) = compute_span_frequencies_and_symbols(&tts[1..]); // Do not include the top subtree. + encode_all(tts.into_iter(), span_frequencies, symbols) } pub fn from_subtree(subtree: SubtreeView<'_>) -> Self { @@ -429,121 +1131,44 @@ impl TopSubtree { } pub fn view(&self) -> SubtreeView<'_> { - let repr = match &self.repr { - TopSubtreeRepr::SpanStorage32(token_trees) => { - TokenTreesReprRef::SpanStorage32(token_trees) - } - TopSubtreeRepr::SpanStorage64(token_trees) => { - TokenTreesReprRef::SpanStorage64(token_trees) - } - TopSubtreeRepr::SpanStorage96(token_trees) => { - TokenTreesReprRef::SpanStorage96(token_trees) - } - }; - SubtreeView(TokenTreesView { repr, span_parts: &self.span_parts }) + let slice = TokenTreesSlice::new(self); + SubtreeView(TokenTreesView { slice, len: self.len }) } pub fn iter(&self) -> TtIter<'_> { self.view().iter() } - pub fn top_subtree(&self) -> crate::Subtree { + pub fn top_subtree(&self) -> Subtree { self.view().top_subtree() } pub fn set_top_subtree_delimiter_kind(&mut self, kind: DelimiterKind) { - dispatch! { - match &mut self.repr => tt => { - let TokenTree::Subtree { delim_kind, .. } = &mut tt[0] else { - unreachable!("the first token tree is always the top subtree"); - }; - *delim_kind = kind; - } - } - } - - fn ensure_can_hold(&mut self, range: TextRange) { - fn can_hold(_: &[TokenTree], range: TextRange) -> bool { - S::can_hold(range, 0) - } - let can_hold = dispatch! { - match &self.repr => tt => can_hold(tt, range) - }; - if can_hold { - return; - } - - // Otherwise, we do something very junky: recreate the entire tree. Hopefully this should be rare. - let mut builder = TopSubtreeBuilder::new(self.top_subtree().delimiter); - builder.extend_with_tt(self.token_trees()); - builder.ensure_can_hold(range, 0); - *self = builder.build(); + change_root_delimiter(&mut self.buffer, kind); } pub fn set_top_subtree_delimiter_span(&mut self, span: DelimSpan) { - self.ensure_can_hold(span.open.range); - self.ensure_can_hold(span.close.range); - fn do_it(tt: &mut [TokenTree], span: DelimSpan) { - let TokenTree::Subtree { open_span, close_span, .. } = &mut tt[0] else { - unreachable!() - }; - *open_span = S::new(span.open.range, 0); - *close_span = S::new(span.close.range, 1); - } - dispatch! { - match &mut self.repr => tt => do_it(tt, span) - } - self.span_parts[0] = CompressedSpanPart::from_span(&span.open); - self.span_parts[1] = CompressedSpanPart::from_span(&span.close); - } - - /// Note: this cannot change spans. - pub fn set_token(&mut self, idx: usize, leaf: crate::Leaf) { - fn do_it( - tt: &mut [TokenTree], - idx: usize, - span_parts: &[CompressedSpanPart], - leaf: crate::Leaf, - ) { - assert!( - !matches!(tt[idx], TokenTree::Subtree { .. }), - "`TopSubtree::set_token()` must be called on a leaf" - ); - let existing_span_compressed = *tt[idx].first_span(); - let existing_span = existing_span_compressed.span(span_parts); - assert_eq!( - *leaf.span(), - existing_span, - "`TopSubtree::set_token()` cannot change spans" + let open_span_idx = self.span_parts.len() - 2; + let close_span_idx = open_span_idx + 1; + unsafe { + change_root_spans( + &mut self.buffer, + open_span_idx as u32, + close_span_idx as u32, + span.open.range, + span.close.range, ); - match leaf { - crate::Leaf::Literal(leaf) => { - tt[idx] = TokenTree::Literal { - text_and_suffix: leaf.text_and_suffix, - span: existing_span_compressed, - kind: leaf.kind, - suffix_len: leaf.suffix_len, - } - } - crate::Leaf::Punct(leaf) => { - tt[idx] = TokenTree::Punct { - char: leaf.char, - spacing: leaf.spacing, - span: existing_span_compressed, - } - } - crate::Leaf::Ident(leaf) => { - tt[idx] = TokenTree::Ident { - sym: leaf.sym, - span: existing_span_compressed, - is_raw: leaf.is_raw, - } - } - } - } - dispatch! { - match &mut self.repr => tt => do_it(tt, idx, &self.span_parts, leaf) } + self.span_parts[open_span_idx] = CompressedSpanPart::from_span(&span.open); + self.span_parts[close_span_idx] = CompressedSpanPart::from_span(&span.close); + } + + /// **Warning**: This is very expensive, this rebuilds the whole tree. Avoid using this if you can. + pub fn set_token(&mut self, idx: usize, leaf: Leaf) { + let mut tts = TokenTreesSlice::new(self).iter().collect::>(); + assert_matches!(tts[idx], TokenTree::Leaf(_), "cannot change a subtree to a leaf"); + tts[idx] = leaf.into(); + *self = TopSubtree::from_serialized(tts); } pub fn token_trees(&self) -> TokenTreesView<'_> { @@ -561,368 +1186,172 @@ impl TopSubtree { } } -#[rust_analyzer::macro_style(braces)] -macro_rules! dispatch_builder { - ( - match $scrutinee:expr => $tt:ident => $body:expr - ) => { - match $scrutinee { - TopSubtreeBuilderRepr::SpanStorage32($tt) => $body, - TopSubtreeBuilderRepr::SpanStorage64($tt) => $body, - TopSubtreeBuilderRepr::SpanStorage96($tt) => $body, - } - }; -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum TopSubtreeBuilderRepr { - SpanStorage32(Vec>), - SpanStorage64(Vec>), - SpanStorage96(Vec>), -} - -type FxIndexSet = indexmap::IndexSet; - -/// In any tree, the first two subtree parts are reserved for the top subtree. -/// -/// We do it because `TopSubtree` exposes an API to modify the top subtree, therefore it's more convenient -/// this way, and it's unlikely to affect memory usage. -const RESERVED_SPAN_PARTS_LEN: usize = 2; - #[derive(Debug, Clone)] pub struct TopSubtreeBuilder { unclosed_subtree_indices: Vec, - token_trees: TopSubtreeBuilderRepr, - span_parts: FxIndexSet, + token_trees: Vec, + span_parts_frequencies: FxHashMap, last_closed_subtree: Option, - /// We need to keep those because they are not inside `span_parts`, see [`RESERVED_SPAN_PARTS_LEN`]. - top_subtree_spans: DelimSpan, + symbol_frequencies: FxHashMap, } impl TopSubtreeBuilder { - pub fn new(top_delimiter: crate::Delimiter) -> Self { - let mut result = Self { - unclosed_subtree_indices: Vec::new(), - token_trees: TopSubtreeBuilderRepr::SpanStorage32(Vec::new()), - span_parts: FxIndexSet::default(), - last_closed_subtree: None, - top_subtree_spans: top_delimiter.delim_span(), + fn insert_span(&mut self, span: &Span) { + *self.span_parts_frequencies.entry(CompressedSpanPart::from_span(span)).or_insert(0) += 1; + } + + fn remove_span(span_parts_frequencies: &mut FxHashMap, span: &Span) { + let hash_map::Entry::Occupied(mut entry) = + span_parts_frequencies.entry(CompressedSpanPart::from_span(span)) + else { + panic!("span not present"); }; - result.ensure_can_hold(top_delimiter.open.range, 0); - result.ensure_can_hold(top_delimiter.close.range, 1); - fn push_first(tt: &mut Vec>, top_delimiter: crate::Delimiter) { - tt.push(TokenTree::Subtree { - len: 0, - delim_kind: top_delimiter.kind, - open_span: S::new(top_delimiter.open.range, 0), - close_span: S::new(top_delimiter.close.range, 1), - }); + *entry.get_mut() -= 1; + if *entry.get() == 0 { + entry.remove(); } - dispatch_builder! { - match &mut result.token_trees => tt => push_first(tt, top_delimiter) - } - result } - fn span_part_index(&mut self, part: CompressedSpanPart) -> usize { - self.span_parts.insert_full(part).0 + RESERVED_SPAN_PARTS_LEN + fn insert_symbol(&mut self, symbol: Symbol) { + *self.symbol_frequencies.entry(symbol).or_insert(0) += 1; } - fn switch_repr>(repr: &mut Vec>) -> Vec> { - let repr = std::mem::take(repr); - repr.into_iter().map(|tt| tt.convert()).collect() + fn remove_symbol(symbol_frequencies: &mut FxHashMap, symbol: Symbol) { + let hash_map::Entry::Occupied(mut entry) = symbol_frequencies.entry(symbol) else { + panic!("span not present"); + }; + *entry.get_mut() -= 1; + if *entry.get() == 0 { + entry.remove(); + } } - /// Ensures we have a representation that can hold these values. - fn ensure_can_hold(&mut self, text_range: TextRange, span_parts_index: usize) { - match &mut self.token_trees { - TopSubtreeBuilderRepr::SpanStorage32(token_trees) => { - if SpanStorage32::can_hold(text_range, span_parts_index) { - // Can hold. - } else if SpanStorage64::can_hold(text_range, span_parts_index) { - self.token_trees = - TopSubtreeBuilderRepr::SpanStorage64(Self::switch_repr(token_trees)); - } else { - self.token_trees = - TopSubtreeBuilderRepr::SpanStorage96(Self::switch_repr(token_trees)); - } - } - TopSubtreeBuilderRepr::SpanStorage64(token_trees) => { - if SpanStorage64::can_hold(text_range, span_parts_index) { - // Can hold. - } else { - self.token_trees = - TopSubtreeBuilderRepr::SpanStorage96(Self::switch_repr(token_trees)); - } - } - TopSubtreeBuilderRepr::SpanStorage96(_) => { - // Can hold anything. - } - } + pub fn new(top_delimiter: Delimiter) -> Self { + let mut result = Self { + unclosed_subtree_indices: Vec::new(), + token_trees: Vec::new(), + span_parts_frequencies: FxHashMap::default(), + last_closed_subtree: None, + symbol_frequencies: FxHashMap::default(), + }; + // Do not insert the top delimiters, they have their own place because we sometimes need to change them. + result.token_trees.push(TokenTree::Subtree(Subtree { delimiter: top_delimiter, len: 0 })); + result } /// Not to be exposed, this assumes the subtree's children will be filled in immediately. - fn push_subtree(&mut self, subtree: crate::Subtree) { - let open_span_parts_index = - self.span_part_index(CompressedSpanPart::from_span(&subtree.delimiter.open)); - self.ensure_can_hold(subtree.delimiter.open.range, open_span_parts_index); - let close_span_parts_index = - self.span_part_index(CompressedSpanPart::from_span(&subtree.delimiter.close)); - self.ensure_can_hold(subtree.delimiter.close.range, close_span_parts_index); - fn do_it( - tt: &mut Vec>, - open_span_parts_index: usize, - close_span_parts_index: usize, - subtree: crate::Subtree, - ) { - let open_span = S::new(subtree.delimiter.open.range, open_span_parts_index); - let close_span = S::new(subtree.delimiter.close.range, close_span_parts_index); - tt.push(TokenTree::Subtree { - len: subtree.len, - delim_kind: subtree.delimiter.kind, - open_span, - close_span, - }); - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, open_span_parts_index, close_span_parts_index, subtree) - } + fn push_subtree(&mut self, subtree: Subtree) { + self.insert_span(&subtree.delimiter.open); + self.insert_span(&subtree.delimiter.close); + self.token_trees.push(subtree.into()); } pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: Span) { - let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&open_span)); - self.ensure_can_hold(open_span.range, span_parts_index); - fn do_it( - token_trees: &mut Vec>, - delimiter_kind: DelimiterKind, - range: TextRange, - span_parts_index: usize, - ) -> usize { - let open_span = S::new(range, span_parts_index); - token_trees.push(TokenTree::Subtree { - len: 0, - delim_kind: delimiter_kind, - open_span, - close_span: open_span, // Will be overwritten on close. - }); - token_trees.len() - 1 - } - let subtree_idx = dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, delimiter_kind, open_span.range, span_parts_index) - }; + self.insert_span(&open_span); + let subtree_idx = self.token_trees.len(); + self.token_trees.push(TokenTree::Subtree(Subtree { + delimiter: Delimiter { open: open_span, close: open_span, kind: delimiter_kind }, + len: 0, // Will be overwritten on close. + })); self.unclosed_subtree_indices.push(subtree_idx); } pub fn close(&mut self, close_span: Span) { - let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&close_span)); - let range = close_span.range; - self.ensure_can_hold(range, span_parts_index); + self.insert_span(&close_span); let last_unclosed_index = self .unclosed_subtree_indices .pop() .expect("attempt to close a `tt::Subtree` when none is open"); - fn do_it( - token_trees: &mut [TokenTree], - last_unclosed_index: usize, - range: TextRange, - span_parts_index: usize, - ) { - let token_trees_len = token_trees.len(); - let TokenTree::Subtree { len, delim_kind: _, open_span: _, close_span } = - &mut token_trees[last_unclosed_index] - else { - unreachable!("unclosed token tree is always a subtree"); - }; - *len = (token_trees_len - last_unclosed_index - 1) as u32; - *close_span = S::new(range, span_parts_index); - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, last_unclosed_index, range, span_parts_index) - } + let token_trees_len = self.token_trees.len(); + let TokenTree::Subtree(Subtree { delimiter: Delimiter { open: _, close, kind: _ }, len }) = + &mut self.token_trees[last_unclosed_index] + else { + unreachable!("unclosed token tree is always a subtree"); + }; + *len = (token_trees_len - last_unclosed_index - 1) as u32; + *close = close_span; self.last_closed_subtree = Some(last_unclosed_index); } /// You cannot call this consecutively, it will only work once after close. pub fn remove_last_subtree_if_invisible(&mut self) { let Some(last_subtree_idx) = self.last_closed_subtree else { return }; - fn do_it(tt: &mut Vec>, last_subtree_idx: usize) { - if let TokenTree::Subtree { delim_kind: DelimiterKind::Invisible, .. } = - tt[last_subtree_idx] - { - tt.remove(last_subtree_idx); - } - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, last_subtree_idx) + if let TokenTree::Subtree(Subtree { + delimiter: Delimiter { kind: DelimiterKind::Invisible, .. }, + .. + }) = self.token_trees[last_subtree_idx] + { + self.token_trees.remove(last_subtree_idx); } self.last_closed_subtree = None; } - fn push_literal(&mut self, leaf: crate::Literal) { - let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); - let range = leaf.span.range; - self.ensure_can_hold(range, span_parts_index); - fn do_it( - tt: &mut Vec>, - range: TextRange, - span_parts_index: usize, - leaf: crate::Literal, - ) { - tt.push(TokenTree::Literal { - text_and_suffix: leaf.text_and_suffix, - span: S::new(range, span_parts_index), - kind: leaf.kind, - suffix_len: leaf.suffix_len, - }) - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) - } - } - - fn push_punct(&mut self, leaf: crate::Punct) { - let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); - let range = leaf.span.range; - self.ensure_can_hold(range, span_parts_index); - fn do_it( - tt: &mut Vec>, - range: TextRange, - span_parts_index: usize, - leaf: crate::Punct, - ) { - tt.push(TokenTree::Punct { - char: leaf.char, - spacing: leaf.spacing, - span: S::new(range, span_parts_index), - }) - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) - } - } - - fn push_ident(&mut self, leaf: crate::Ident) { - let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); - let range = leaf.span.range; - self.ensure_can_hold(range, span_parts_index); - fn do_it( - tt: &mut Vec>, - range: TextRange, - span_parts_index: usize, - leaf: crate::Ident, - ) { - tt.push(TokenTree::Ident { - sym: leaf.sym, - span: S::new(range, span_parts_index), - is_raw: leaf.is_raw, - }) - } - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) - } - } - - pub fn push(&mut self, leaf: crate::Leaf) { - match leaf { - crate::Leaf::Literal(leaf) => self.push_literal(leaf), - crate::Leaf::Punct(leaf) => self.push_punct(leaf), - crate::Leaf::Ident(leaf) => self.push_ident(leaf), - } - } - - fn push_token_tree(&mut self, tt: crate::TokenTree) { + pub fn push(&mut self, leaf: Leaf) { + self.insert_span(leaf.span()); + if let Some(symbol) = leaf.symbol() { + self.insert_symbol(symbol.clone()); + } + self.token_trees.push(leaf.into()); + } + + fn push_token_tree(&mut self, tt: TokenTree) { match tt { - crate::TokenTree::Leaf(leaf) => self.push(leaf), - crate::TokenTree::Subtree(subtree) => self.push_subtree(subtree), + TokenTree::Leaf(leaf) => self.push(leaf), + TokenTree::Subtree(subtree) => self.push_subtree(subtree), } } - pub fn extend(&mut self, leaves: impl IntoIterator) { + pub fn extend(&mut self, leaves: impl IntoIterator) { leaves.into_iter().for_each(|leaf| self.push(leaf)); } pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) { - fn do_it( - this: &mut TopSubtreeBuilder, - tt: &[TokenTree], - span_parts: &[CompressedSpanPart], - ) { - for tt in tt { - this.push_token_tree(tt.to_api(span_parts)); - } - } - dispatch_ref! { - match tt.repr => tt_repr => do_it(self, tt_repr, tt.span_parts) - } + tt.iter_flat_tokens().for_each(|tt| self.push_token_tree(tt)); } /// Like [`Self::extend_with_tt()`], but makes sure the new tokens will never be /// joint with whatever comes after them. pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_>) { self.extend_with_tt(tt); - fn do_it(tt: &mut [TokenTree]) { - if let Some(TokenTree::Punct { spacing, .. }) = tt.last_mut() { - *spacing = Spacing::Alone; - } - } - if !tt.is_empty() { - dispatch_builder! { - match &mut self.token_trees => tt => do_it(tt) - } + if !tt.is_empty() + && let Some(TokenTree::Leaf(Leaf::Punct(Punct { spacing, .. }))) = + self.token_trees.last_mut() + { + *spacing = Spacing::Alone; } } pub fn expected_delimiters(&self) -> impl Iterator { self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| { - dispatch_builder! { - match &self.token_trees => tt => { - let TokenTree::Subtree { delim_kind, .. } = tt[subtree_idx] else { - unreachable!("unclosed token tree is always a subtree") - }; - delim_kind - } - } + let TokenTree::Subtree(Subtree { delimiter, .. }) = self.token_trees[subtree_idx] + else { + unreachable!("unclosed token tree is always a subtree") + }; + delimiter.kind }) } /// Builds, and remove the top subtree if it has only one subtree child. pub fn build_skip_top_subtree(mut self) -> TopSubtree { - fn remove_first_if_needed( - tt: &mut Vec>, - top_delim_span: &mut DelimSpan, - span_parts: &FxIndexSet, - ) { - let tt_len = tt.len(); - let Some(TokenTree::Subtree { len, open_span, close_span, .. }) = tt.get_mut(1) else { - return; - }; - if (*len as usize) != (tt_len - 2) { - // Subtree does not cover the whole tree (minus 2; itself, and the top span). - return; - } - - // Now we need to adjust the spans, because we assume that the first two spans are always reserved. - let top_open_span = span_parts - .get_index(open_span.span_parts_index() - RESERVED_SPAN_PARTS_LEN) - .unwrap() - .recombine(open_span.text_range()); - let top_close_span = span_parts - .get_index(close_span.span_parts_index() - RESERVED_SPAN_PARTS_LEN) - .unwrap() - .recombine(close_span.text_range()); - *top_delim_span = DelimSpan { open: top_open_span, close: top_close_span }; - // Can't remove the top spans from the map, as maybe they're used by other things as well. - // Now we need to reencode the spans, because their parts index changed: - *open_span = S::new(open_span.text_range(), 0); - *close_span = S::new(close_span.text_range(), 1); - - tt.remove(0); - } - dispatch_builder! { - match &mut self.token_trees => tt => remove_first_if_needed(tt, &mut self.top_subtree_spans, &self.span_parts) + assert!( + self.unclosed_subtree_indices.is_empty(), + "attempt to build an unbalanced `TopSubtreeBuilder`" + ); + let tt_len = self.token_trees.len(); + if let Some(&TokenTree::Subtree(Subtree { len, delimiter })) = self.token_trees.get(1) + && (len as usize) == (tt_len - 2) + { + // The top subtree's delimiters should not be included. + Self::remove_span(&mut self.span_parts_frequencies, &delimiter.open); + Self::remove_span(&mut self.span_parts_frequencies, &delimiter.close); + + let mut token_trees = self.token_trees.into_iter(); + token_trees.next(); // Remove the first subtree. + encode_all(token_trees, self.span_parts_frequencies, self.symbol_frequencies) + } else { + self.build() } - self.build() } pub fn build(mut self) -> TopSubtree { @@ -930,57 +1359,52 @@ impl TopSubtreeBuilder { self.unclosed_subtree_indices.is_empty(), "attempt to build an unbalanced `TopSubtreeBuilder`" ); - fn finish_top_len(tt: &mut [TokenTree]) { - let total_len = tt.len() as u32; - let TokenTree::Subtree { len, .. } = &mut tt[0] else { - unreachable!("first token tree is always a subtree"); - }; - *len = total_len - 1; - } - dispatch_builder! { - match &mut self.token_trees => tt => finish_top_len(tt) - } - - let span_parts = [ - CompressedSpanPart::from_span(&self.top_subtree_spans.open), - CompressedSpanPart::from_span(&self.top_subtree_spans.close), - ] - .into_iter() - .chain(self.span_parts.iter().copied()) - .collect(); - - let repr = match self.token_trees { - TopSubtreeBuilderRepr::SpanStorage32(tt) => { - TopSubtreeRepr::SpanStorage32(tt.into_boxed_slice()) - } - TopSubtreeBuilderRepr::SpanStorage64(tt) => { - TopSubtreeRepr::SpanStorage64(tt.into_boxed_slice()) - } - TopSubtreeBuilderRepr::SpanStorage96(tt) => { - TopSubtreeRepr::SpanStorage96(tt.into_boxed_slice()) - } + let tts_len = self.token_trees.len(); + let TokenTree::Subtree(top_subtree) = &mut self.token_trees[0] else { + panic!("first token tree must be a subtree"); }; - - TopSubtree { repr, span_parts } + top_subtree.len = (tts_len - 1).try_into().unwrap(); + encode_all( + self.token_trees.into_iter(), + self.span_parts_frequencies, + self.symbol_frequencies, + ) } - pub fn restore_point(&self) -> SubtreeBuilderRestorePoint { - let token_trees_len = dispatch_builder! { - match &self.token_trees => tt => tt.len() - }; + pub fn restore_point(&mut self) -> SubtreeBuilderRestorePoint { + // We reset the `last_closed_subtree`, since restoring from a restore point doesn't play well with removing the last subtree. + self.last_closed_subtree = None; SubtreeBuilderRestorePoint { unclosed_subtree_indices_len: self.unclosed_subtree_indices.len(), - token_trees_len, - last_closed_subtree: self.last_closed_subtree, + token_trees_len: self.token_trees.len(), } } pub fn restore(&mut self, restore_point: SubtreeBuilderRestorePoint) { - self.unclosed_subtree_indices.truncate(restore_point.unclosed_subtree_indices_len); - dispatch_builder! { - match &mut self.token_trees => tt => tt.truncate(restore_point.token_trees_len) + if restore_point.token_trees_len >= self.token_trees.len() { + // This means we restored twice, potentially with an earlier restore point first. + return; + } + + for tt in &self.token_trees[restore_point.token_trees_len..] { + match tt { + TokenTree::Leaf(leaf) => { + Self::remove_span(&mut self.span_parts_frequencies, leaf.span()); + + if let Some(symbol) = leaf.symbol() { + Self::remove_symbol(&mut self.symbol_frequencies, symbol.clone()); + } + } + TokenTree::Subtree(subtree) => { + Self::remove_span(&mut self.span_parts_frequencies, &subtree.delimiter.open); + Self::remove_span(&mut self.span_parts_frequencies, &subtree.delimiter.close); + } + } } - self.last_closed_subtree = restore_point.last_closed_subtree; + + self.unclosed_subtree_indices.truncate(restore_point.unclosed_subtree_indices_len); + self.token_trees.truncate(restore_point.token_trees_len); + self.last_closed_subtree = None; } } @@ -988,5 +1412,4 @@ impl TopSubtreeBuilder { pub struct SubtreeBuilderRestorePoint { unclosed_subtree_indices_len: usize, token_trees_len: usize, - last_closed_subtree: Option, } From 798009c502496ce5689184ee1846ed7408a7e6fc Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 17 Aug 2026 09:19:32 +0800 Subject: [PATCH 03/26] Add a flag do not parse rest arguments --- .../src/macro_expansion_tests/builtin_fn_macro.rs | 12 ++++++++++-- .../crates/hir-expand/src/builtin/fn_macro.rs | 15 ++++++++++----- 2 files changed, 20 insertions(+), 7 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 d6ccf9ca51ab0..7120980dd30cf 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 @@ -160,13 +160,21 @@ fn test_option_env_expand() { #[rustc_builtin_macro] macro_rules! option_env {() => {}} -fn main() { option_env!("TEST_ENV_VAR"); } +fn main() { + option_env!("TEST_ENV_VAR"); + option_env!("TEST_ENV_VAR",); + option_env!("TEST_ENV_VAR", "invalid"); +} "#, expect![[r#" #[rustc_builtin_macro] macro_rules! option_env {() => {}} -fn main() { $crate::option::Option::None:: < &str>; } +fn main() { + $crate::option::Option::None:: < &str>; + $crate::option::Option::None:: < &str>; + /* error: unexpected input */; +} "#]], ); } 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 7a162fcf4bcb7..a91a1b08b6963 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 @@ -766,7 +766,7 @@ fn relative_file( } } -fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { +fn parse_string(tt: &tt::TopSubtree, rest: bool) -> Result<(Symbol, Span), ExpandError> { let expect_literal = |span| ExpandError::other(span, "expected string literal"); let mut tt = { let mut tt_iter = tt.iter(); @@ -778,6 +778,11 @@ fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { Some(TtElement::Leaf(tt::Leaf::Punct(it))) if it.char == ',' => { // Tail comma // FIXME: Ignored like env!("NAME", "compile_error message") + if let Some(tt) = tt_iter.next() + && !rest + { + return Err(ExpandError::other(tt.first_span(), "unexpected input")); + } } Some(tt) => { return Err(ExpandError::other(tt.first_span(), "unexpected input")); @@ -846,7 +851,7 @@ pub fn include_input_to_file_id( arg_id: MacroCallId, arg: &tt::TopSubtree, ) -> Result { - let (s, span) = parse_string(arg)?; + let (s, span) = parse_string(arg, false)?; relative_file(db, arg_id, s.as_str(), false, span) } @@ -870,7 +875,7 @@ fn include_str_expand( tt: &tt::TopSubtree, call_site: Span, ) -> ExpandResult { - let (path, input_span) = match parse_string(tt) { + let (path, input_span) = match parse_string(tt, false) { Ok(it) => it, Err(e) => { return ExpandResult::new( @@ -908,7 +913,7 @@ fn env_expand( tt: &tt::TopSubtree, span: Span, ) -> ExpandResult { - let (key, span) = match parse_string(tt) { + let (key, span) = match parse_string(tt, true) { Ok(it) => it, Err(e) => { return ExpandResult::new( @@ -946,7 +951,7 @@ fn option_env_expand( tt: &tt::TopSubtree, call_site: Span, ) -> ExpandResult { - let (key, span) = match parse_string(tt) { + let (key, span) = match parse_string(tt, false) { Ok(it) => it, Err(e) => { return ExpandResult::new( From c6e5196a0bf667b32916820a61cc4eccbd9b5570 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 17 Aug 2026 09:45:48 +0800 Subject: [PATCH 04/26] Rename 'rest' to 'allow_rest_args' --- .../rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a91a1b08b6963..44579304d3646 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 @@ -766,7 +766,7 @@ fn relative_file( } } -fn parse_string(tt: &tt::TopSubtree, rest: bool) -> Result<(Symbol, Span), ExpandError> { +fn parse_string(tt: &tt::TopSubtree, allow_rest_args: bool) -> Result<(Symbol, Span), ExpandError> { let expect_literal = |span| ExpandError::other(span, "expected string literal"); let mut tt = { let mut tt_iter = tt.iter(); @@ -779,7 +779,7 @@ fn parse_string(tt: &tt::TopSubtree, rest: bool) -> Result<(Symbol, Span), Expan // Tail comma // FIXME: Ignored like env!("NAME", "compile_error message") if let Some(tt) = tt_iter.next() - && !rest + && !allow_rest_args { return Err(ExpandError::other(tt.first_span(), "unexpected input")); } From e5cb09e62bfd654113338bd8188a5551db39b03e Mon Sep 17 00:00:00 2001 From: kivancgnlp Date: Mon, 17 Aug 2026 03:38:59 +0000 Subject: [PATCH 05/26] ide-diagnostics: emit E0600 for unary operator on unsupported type Addresses the FIXME in `hir-ty/src/infer/op.rs` inside `infer_user_unop`, which previously silently discarded operator method resolution failures for `!x` and `-x` expressions. When the operand's type does not implement `std::ops::Not` (for `!`) or `std::ops::Neg` (for `-`), rust-analyzer now reports the same E0600 error that rustc produces: cannot apply unary operator `!` to type `Question` Wired through the standard inference diagnostic pipeline: new `InferenceDiagnostic::UnaryOperatorCannotBeApplied` variant in hir-ty, matching `UnaryOperatorCannotBeApplied` struct plus conversion in hir, and a handler in ide-diagnostics using `DiagnosticCode::RustcHardError("E0600")`. Filtering for unresolved / error-typed operands is done in `resolve_diagnostics()` (crates/hir-ty/src/infer/unify.rs) alongside the existing `references_non_lt_error()` filter chain for other diagnostics that carry a type. This keeps `infer_user_unop` free of callsite guards and lets the natural inference pipeline suppress spurious reports on incomplete code and on macro expansions that infer to `{unknown}`. The `unary_ops` region of `test-utils/src/minicore.rs` also gains builtin `Not` and `Neg` impls, mirroring how `add_impl!` provides them in the `add` region. Without these, the diagnostic test fixture would incorrectly flag `!true`, `!0i32` and similar builtin uses as errors, because `lookup_op_method` would find no impl in the minicore fixture even though real `core` has one. With the impls present, primitives resolve normally and only genuinely unsupported operators trigger the diagnostic. This also lets us correctly report `-1u32` as E0600, since real `core` does not implement `Neg` for unsigned integers. Because the new `not_impl!` / `neg_impl!` blocks live in a nested `region:builtin_impls` inside `region:unary_ops`, the new tests opt into both flags via `//- minicore: unary_ops, builtin_impls`. The existing `legacy_const_generics` test in `mismatched_arg_count` uses `-1i32` / `-1i8` inline and now needs the same directive so that `core::ops::Neg` is in scope for its operands. Minicore `region:eq` and `region:float_consts` now depend on `unary_ops, builtin_impls` so their smoke tests resolve `Not`/`Neg` without per-callsite guards. The `Clone for [T; 1]` impl inside `region:builtin_impls` uses `self[0]`, so it is scoped to a nested `region:index` and only compiles when `index` is also enabled. The `UnaryOp::Deref` case is left unchanged; it is already handled by the `CannotBeDereferenced` diagnostic (E0614) and `infer_user_unop` is never called for `Deref`. Part of rust-lang/rust-analyzer#22140. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 9 +- .../crates/hir-ty/src/infer/op.rs | 8 +- .../crates/hir-ty/src/infer/unify.rs | 1 + .../crates/hir/src/diagnostics.rs | 12 ++ .../src/handlers/mismatched_arg_count.rs | 1 + .../unary_operator_cannot_be_applied.rs | 158 ++++++++++++++++++ .../crates/ide-diagnostics/src/lib.rs | 2 + .../crates/test-utils/src/minicore.rs | 30 +++- 8 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unary_operator_cannot_be_applied.rs diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index a719b364a872e..1b0eaffbdeaea 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -47,7 +47,7 @@ use hir_def::{ TupleFieldId, TupleId, VariantId, attrs::AttrFlags, expr_store::{Body, ExpressionStore, HygieneId, body::Param, path::Path}, - hir::{BindingId, ExprId, ExprOrPatId, ExprOrPatIdPacked, LabelId, PatId}, + hir::{BindingId, ExprId, ExprOrPatId, ExprOrPatIdPacked, LabelId, PatId, UnaryOp}, lang_item::LangItems, layout::Integer, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, @@ -434,6 +434,13 @@ pub enum InferenceDiagnostic { expr: ExprId, found: StoredTy, }, + UnaryOperatorCannotBeApplied { + #[type_visitable(ignore)] + expr: ExprId, + #[type_visitable(ignore)] + op: UnaryOp, + found: StoredTy, + }, MutRefInImmRefPat { #[type_visitable(ignore)] pat: PatId, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs index 5fd4e830fb172..16e62e9dace48 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs @@ -9,7 +9,7 @@ use syntax::ast::{ArithOp, BinaryOp, UnaryOp}; use tracing::debug; use crate::{ - Adjust, Adjustment, AutoBorrow, + Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, infer::{AllowTwoPhase, AutoBorrowMutability, Expectation, InferenceContext, expr::ExprIsRead}, method_resolution::{MethodCallee, TreatNotYetDefinedOpaques}, next_solver::{ @@ -271,7 +271,11 @@ impl<'db> InferenceContext<'db> { method.sig.output() } Err(_errors) => { - // FIXME: Report diagnostic. + self.push_diagnostic(InferenceDiagnostic::UnaryOperatorCannotBeApplied { + expr: ex, + op, + found: operand_ty.store(), + }); self.types.types.error } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index 7b589efba2f63..8070ed8788977 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -586,6 +586,7 @@ pub(super) mod resolve_completely { | InferenceDiagnostic::CannotIndexInto { found: ty, .. } | InferenceDiagnostic::ExpectedFunction { found: ty, .. } | InferenceDiagnostic::ExpectedArrayOrSlicePat { found: ty, .. } + | InferenceDiagnostic::UnaryOperatorCannotBeApplied { found: ty, .. } | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } | InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic && ty.as_ref().references_non_lt_error() diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index f6df18a6fb577..3144db5817272 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -105,6 +105,7 @@ diagnostics![AnyDiagnostic<'db> -> AwaitOutsideOfAsync, BreakOutsideOfLoop, CannotBeDereferenced<'db>, + UnaryOperatorCannotBeApplied<'db>, CannotImplicitlyDerefTraitObject<'db>, CannotIndexInto<'db>, CastToUnsized<'db>, @@ -338,6 +339,13 @@ pub struct CannotBeDereferenced<'db> { pub found: Type<'db>, } +#[derive(Debug)] +pub struct UnaryOperatorCannotBeApplied<'db> { + pub expr: InFile, + pub op: ast::UnaryOp, + pub found: Type<'db>, +} + #[derive(Debug)] pub struct MutRefInImmRefPat { pub pat: InFile, @@ -985,6 +993,10 @@ impl<'db> AnyDiagnostic<'db> { let expr = expr_syntax(*expr)?; CannotBeDereferenced { expr, found: new_ty(found.as_ref()) }.into() } + InferenceDiagnostic::UnaryOperatorCannotBeApplied { expr, op, found } => { + let expr = expr_syntax(*expr)?; + UnaryOperatorCannotBeApplied { expr, op: *op, found: new_ty(found.as_ref()) }.into() + } InferenceDiagnostic::MutRefInImmRefPat { pat } => { let pat = pat_syntax(*pat)?.map(Into::into); MutRefInImmRefPat { pat }.into() diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index fb9095e0f4bd2..a577353e27cf0 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -488,6 +488,7 @@ fn main() { fn legacy_const_generics() { check_diagnostics( r#" +//- minicore: unary_ops, builtin_impls #[rustc_legacy_const_generics(1, 3)] fn mixed( _a: u8, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unary_operator_cannot_be_applied.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unary_operator_cannot_be_applied.rs new file mode 100644 index 0000000000000..1dc779d43ac0f --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unary_operator_cannot_be_applied.rs @@ -0,0 +1,158 @@ +use hir::HirDisplay; +use syntax::ast::UnaryOp; + +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: unary-operator-cannot-be-applied +// +// This diagnostic is triggered if a unary operator (`!` or `-`) is applied +// to a value whose type does not implement the corresponding trait +// (`Not` or `Neg`). +pub(crate) fn unary_operator_cannot_be_applied( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::UnaryOperatorCannotBeApplied<'_>, +) -> Diagnostic { + let op = match d.op { + UnaryOp::Not => "!", + UnaryOp::Neg => "-", + // `Deref` uses a different diagnostic (`CannotBeDereferenced`). + UnaryOp::Deref => "*", + }; + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0600"), + format!( + "cannot apply unary operator `{op}` to type `{}`", + d.found.display(ctx.sema.db, ctx.display_target) + ), + d.expr.map(Into::into), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn not_on_enum() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +enum Question { Yes, No } + +fn f() { + let _ = !Question::Yes; + //^^^^^^^^^^^^^^ error: cannot apply unary operator `!` to type `Question` +} +"#, + ); + } + + #[test] + fn neg_on_struct() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +struct S; + +fn f() { + let _ = -S; + //^^ error: cannot apply unary operator `-` to type `S` +} +"#, + ); + } + + #[test] + fn allows_not_on_bool() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +fn f() { + let _ = !true; + let _ = !false; +} +"#, + ); + } + + #[test] + fn allows_not_on_integer() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +fn f() { + let _ = !0u32; + let _ = !0i32; +} +"#, + ); + } + + #[test] + fn allows_neg_on_numeric() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +fn f() { + let _ = -1i32; + let _ = -1.0f64; +} +"#, + ); + } + + #[test] + fn neg_on_unsigned() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +fn f() { + let _ = -1u32; + //^^^^^ error: cannot apply unary operator `-` to type `u32` +} +"#, + ); + } + + #[test] + fn allows_not_with_impl() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +struct Bar; +struct Foo; + +impl core::ops::Not for Bar { + type Output = Foo; + fn not(self) -> Foo { Foo } +} + +fn f() { + let _ = !Bar; +} +"#, + ); + } + + #[test] + fn allows_neg_with_impl() { + check_diagnostics( + r#" +//- minicore: unary_ops, builtin_impls +struct Bar; +struct Foo; + +impl core::ops::Neg for Bar { + type Output = Foo; + fn neg(self) -> Foo { Foo } +} + +fn f() { + let _ = -Bar; +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index 8ba59edcbc871..5d816a8d41c3c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -85,6 +85,7 @@ mod handlers { pub(crate) mod type_mismatch; pub(crate) mod type_must_be_known; pub(crate) mod typed_hole; + pub(crate) mod unary_operator_cannot_be_applied; pub(crate) mod undeclared_label; pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unimplemented_trait; @@ -437,6 +438,7 @@ pub fn semantic_diagnostics( let d = match diag { AnyDiagnostic::AwaitOutsideOfAsync(d) => handlers::await_outside_of_async::await_outside_of_async(&ctx, &d), AnyDiagnostic::CannotBeDereferenced(d) => handlers::cannot_be_dereferenced::cannot_be_dereferenced(&ctx, &d), + AnyDiagnostic::UnaryOperatorCannotBeApplied(d) => handlers::unary_operator_cannot_be_applied::unary_operator_cannot_be_applied(&ctx, &d), AnyDiagnostic::CannotImplicitlyDerefTraitObject(d) => handlers::cannot_implicitly_deref_trait_object::cannot_implicitly_deref_trait_object(&ctx, &d), AnyDiagnostic::CannotIndexInto(d) => handlers::cannot_index_into::cannot_index_into(&ctx, &d), AnyDiagnostic::CastToUnsized(d) => handlers::invalid_cast::cast_to_unsized(&ctx, &d), diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 2e0994a1885d9..0d9bb4f92bdc0 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -34,9 +34,9 @@ //! discriminant: //! drop: sized //! env: option -//! eq: sized +//! eq: sized, unary_ops, builtin_impls //! error: fmt -//! float_consts: +//! float_consts: unary_ops, builtin_impls //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive //! fn: sized, tuple //! from: sized, result @@ -370,11 +370,13 @@ pub mod clone { } } + // region:index impl Clone for [T; 1] { fn clone(&self) -> Self { [self[0].clone()] } } + // endregion:index // endregion:builtin_impls // region:derive @@ -1213,6 +1215,30 @@ pub mod ops { #[must_use = "this returns the result of the operation, without modifying the original"] fn neg(self) -> Self::Output; } + + // region:builtin_impls + macro_rules! not_impl { + ($($t:ty)*) => ($( + impl const Not for $t { + type Output = $t; + fn not(self) -> $t { !self } + } + )*) + } + + not_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 } + + macro_rules! neg_impl { + ($($t:ty)*) => ($( + impl const Neg for $t { + type Output = $t; + fn neg(self) -> $t { -self } + } + )*) + } + + neg_impl! { isize i8 i16 i32 i64 i128 f16 f32 f64 f128 } + // endregion:builtin_impls // endregion:unary_ops // region:coroutine From 55f8db6f9a99a4414c917a715224dfc4fe390d87 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 17 Aug 2026 04:21:44 +0000 Subject: [PATCH 06/26] Prepare for merging from rust-lang/rust This updates the rust-version file to 2c39ff499469be916d4e45506d1afed69bbaddb7. --- 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 f930573748e28..2f175e966812d 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -7fb284d9037fa54f6a9b24261c82b394472cbfd7 +2c39ff499469be916d4e45506d1afed69bbaddb7 From 642c35c0d82ec4fddd8e2c2d388c62d435342baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 17 Aug 2026 11:24:06 +0300 Subject: [PATCH 07/26] Download all artifacts in a single step --- .../.github/workflows/metrics.yaml | 30 ++-------------- .../.github/workflows/release.yaml | 35 ++----------------- 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/metrics.yaml b/src/tools/rust-analyzer/.github/workflows/metrics.yaml index a482235105c04..51a12386088ac 100644 --- a/src/tools/rust-analyzer/.github/workflows/metrics.yaml +++ b/src/tools/rust-analyzer/.github/workflows/metrics.yaml @@ -91,35 +91,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Download build metrics + - name: Download metrics uses: actions/download-artifact@v8 with: - name: build-${{ github.sha }} - - - name: Download self metrics - uses: actions/download-artifact@v8 - with: - name: self-${{ github.sha }} - - - name: Download ripgrep-13.0.0 metrics - uses: actions/download-artifact@v8 - with: - name: ripgrep-13.0.0-${{ github.sha }} - - - name: Download webrender-2022 metrics - uses: actions/download-artifact@v8 - with: - name: webrender-2022-${{ github.sha }} - - - name: Download diesel-1.4.8 metrics - uses: actions/download-artifact@v8 - with: - name: diesel-1.4.8-${{ github.sha }} - - - name: Download hyper-0.14.18 metrics - uses: actions/download-artifact@v8 - with: - name: hyper-0.14.18-${{ github.sha }} + pattern: '*-${{ github.sha }}' + merge-multiple: true - name: Combine json run: | diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 50af766db3cbd..e6e459d7eeb6f 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -237,40 +237,9 @@ jobs: - uses: actions/download-artifact@v8 with: - name: dist-aarch64-apple-darwin - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-x86_64-apple-darwin - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-x86_64-unknown-linux-gnu - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-x86_64-unknown-linux-musl - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-aarch64-unknown-linux-gnu - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-arm-unknown-linux-gnueabihf - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-x86_64-pc-windows-msvc - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-i686-pc-windows-msvc - path: dist - - uses: actions/download-artifact@v8 - with: - name: dist-aarch64-pc-windows-msvc + pattern: dist-* path: dist + merge-multiple: true - run: ls -al ./dist - name: Publish Release From 2e32d85ac962fb22df6eaaeaf222f3e62cd7fe29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 17 Aug 2026 12:14:27 +0300 Subject: [PATCH 08/26] Drop zigbuild support --- .../.github/workflows/release.yaml | 1 - src/tools/rust-analyzer/xtask/src/dist.rs | 33 ++++--------------- src/tools/rust-analyzer/xtask/src/flags.rs | 3 -- src/tools/rust-analyzer/xtask/src/pgo.rs | 9 ++--- 4 files changed, 9 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 50af766db3cbd..d9904a6dec459 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -40,7 +40,6 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu # Use a container with glibc 2.28 - # Zig is not used because it doesn't work with PGO container: quay.io/pypa/manylinux_2_28_x86_64 code-target: linux-x64 allocator: system diff --git a/src/tools/rust-analyzer/xtask/src/dist.rs b/src/tools/rust-analyzer/xtask/src/dist.rs index e8bedbe79e56e..1f1254286d1c5 100644 --- a/src/tools/rust-analyzer/xtask/src/dist.rs +++ b/src/tools/rust-analyzer/xtask/src/dist.rs @@ -43,7 +43,6 @@ impl flags::Dist { &format!("{version}-standalone"), &target, allocator, - self.zig, self.pgo, // Profiling requires debug information. self.enable_profiling, @@ -56,7 +55,6 @@ impl flags::Dist { "0.0.0-standalone", &target, allocator, - self.zig, self.pgo, // Profiling requires debug information. self.enable_profiling, @@ -101,7 +99,6 @@ fn dist_server( release: &str, target: &Target, allocator: Malloc, - zig: bool, pgo: Option, dev_rel: bool, ) -> anyhow::Result<()> { @@ -116,33 +113,27 @@ fn dist_server( // * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable. // let _e = sh.push_env("CARGO_PROFILE_RELEASE_DEBUG", "1"); - let linux_target = target.is_linux(); - let target_name = match &target.libc_suffix { - Some(libc_suffix) if zig => format!("{}.{libc_suffix}", target.name), - _ => target.name.to_owned(), - }; let features = allocator.to_features(); - let command = if linux_target && zig { "zigbuild" } else { "build" }; let pgo_profile = if let Some(train_crate) = pgo { Some(crate::pgo::gather_pgo_profile( sh, - crate::pgo::build_command(sh, command, &target_name, features), - &target_name, + crate::pgo::build_command(sh, &target.name, features), + &target.name, train_crate, )?) } else { None }; - let mut cmd = build_command(sh, command, &target_name, features, dev_rel); + let mut cmd = build_command(sh, &target.name, features, dev_rel); let mut rustflags = Vec::new(); if let Some(profile) = pgo_profile { rustflags.push(format!("-Cprofile-use={}", profile.to_str().unwrap())); } - if target_name.ends_with("-windows-msvc") { + if target.name.ends_with("-windows-msvc") { // https://github.com/rust-lang/rust-analyzer/issues/20970 rustflags.push("-Ctarget-feature=+crt-static".to_owned()); } @@ -153,7 +144,7 @@ fn dist_server( cmd.run().context("cannot build Rust Analyzer")?; let dst = Path::new("dist").join(&target.artifact_name); - if target_name.contains("-windows-") { + if target.name.contains("-windows-") { zip(&target.server_path, target.symbols_path.as_ref(), &dst.with_extension("zip"))?; } else { gzip(&target.server_path, &dst.with_extension("gz"))?; @@ -164,7 +155,6 @@ fn dist_server( fn build_command<'a>( sh: &'a Shell, - command: &str, target_name: &str, features: &[&str], dev_rel: bool, @@ -172,7 +162,7 @@ fn build_command<'a>( let profile = if dev_rel { "dev-rel" } else { "release" }; cmd!( sh, - "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --profile {profile}" + "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --profile {profile}" ) } @@ -222,7 +212,6 @@ fn zip(src_path: &Path, symbols_path: Option<&PathBuf>, dest_path: &Path) -> any struct Target { name: String, - libc_suffix: Option, server_path: PathBuf, symbols_path: Option, artifact_name: String, @@ -231,10 +220,6 @@ struct Target { impl Target { fn get(project_root: &Path, sh: &Shell) -> Self { let name = detect_target(sh); - let (name, libc_suffix) = match name.split_once('.') { - Some((l, r)) => (l.to_owned(), Some(r.to_owned())), - None => (name, None), - }; let out_path = project_root.join("target").join(&name).join("release"); let (exe_suffix, symbols_path) = if name.contains("-windows-") { (".exe".into(), Some(out_path.join("rust_analyzer.pdb"))) @@ -243,11 +228,7 @@ impl Target { }; let server_path = out_path.join(format!("rust-analyzer{exe_suffix}")); let artifact_name = format!("rust-analyzer-{name}{exe_suffix}"); - Self { name, libc_suffix, server_path, symbols_path, artifact_name } - } - - fn is_linux(&self) -> bool { - self.name.contains("-linux-") + Self { name, server_path, symbols_path, artifact_name } } } diff --git a/src/tools/rust-analyzer/xtask/src/flags.rs b/src/tools/rust-analyzer/xtask/src/flags.rs index e72d8f22e4f0a..ecf38666934a0 100644 --- a/src/tools/rust-analyzer/xtask/src/flags.rs +++ b/src/tools/rust-analyzer/xtask/src/flags.rs @@ -76,8 +76,6 @@ xflags::xflags! { // **Warning:** This will produce a slower build of rust-analyzer, use only for profiling. optional --enable-profiling optional --client-patch-version version: String - /// Use cargo-zigbuild - optional --zig /// Apply PGO optimizations optional --pgo pgo: PgoTrainingCrate } @@ -154,7 +152,6 @@ pub struct Dist { pub jemalloc: bool, pub enable_profiling: bool, pub client_patch_version: Option, - pub zig: bool, pub pgo: Option, } diff --git a/src/tools/rust-analyzer/xtask/src/pgo.rs b/src/tools/rust-analyzer/xtask/src/pgo.rs index ca6dace940b54..c8bb1417df94d 100644 --- a/src/tools/rust-analyzer/xtask/src/pgo.rs +++ b/src/tools/rust-analyzer/xtask/src/pgo.rs @@ -97,15 +97,10 @@ fn download_crate_for_training(sh: &Shell, pgo_dir: &Path, repo: &str) -> anyhow } /// Helper function to create a build command for rust-analyzer -pub(crate) fn build_command<'a>( - sh: &'a Shell, - command: &str, - target_name: &str, - features: &[&str], -) -> Cmd<'a> { +pub(crate) fn build_command<'a>(sh: &'a Shell, target_name: &str, features: &[&str]) -> Cmd<'a> { cmd!( sh, - "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" + "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" ) } From d458b39fad35d5a5113d6c07f0f9b885e961cd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 17 Aug 2026 13:57:59 +0300 Subject: [PATCH 09/26] Drop duplicate function --- src/tools/rust-analyzer/xtask/src/dist.rs | 8 ++------ src/tools/rust-analyzer/xtask/src/pgo.rs | 8 -------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/xtask/src/dist.rs b/src/tools/rust-analyzer/xtask/src/dist.rs index 1f1254286d1c5..0b8b14cb22717 100644 --- a/src/tools/rust-analyzer/xtask/src/dist.rs +++ b/src/tools/rust-analyzer/xtask/src/dist.rs @@ -115,13 +115,9 @@ fn dist_server( let features = allocator.to_features(); + let cmd = build_command(sh, &target.name, features, dev_rel); let pgo_profile = if let Some(train_crate) = pgo { - Some(crate::pgo::gather_pgo_profile( - sh, - crate::pgo::build_command(sh, &target.name, features), - &target.name, - train_crate, - )?) + Some(crate::pgo::gather_pgo_profile(sh, cmd, &target.name, train_crate)?) } else { None }; diff --git a/src/tools/rust-analyzer/xtask/src/pgo.rs b/src/tools/rust-analyzer/xtask/src/pgo.rs index c8bb1417df94d..9eb41faf26019 100644 --- a/src/tools/rust-analyzer/xtask/src/pgo.rs +++ b/src/tools/rust-analyzer/xtask/src/pgo.rs @@ -96,14 +96,6 @@ fn download_crate_for_training(sh: &Shell, pgo_dir: &Path, repo: &str) -> anyhow Ok(target_path) } -/// Helper function to create a build command for rust-analyzer -pub(crate) fn build_command<'a>(sh: &'a Shell, target_name: &str, features: &[&str]) -> Cmd<'a> { - cmd!( - sh, - "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" - ) -} - pub(crate) fn apply_pgo_to_cmd<'a>(cmd: Cmd<'a>, profile_path: &Path) -> Cmd<'a> { cmd.env("RUSTFLAGS", format!("-Cprofile-use={}", profile_path.to_str().unwrap())) } From 3e3dd5355c5ed4c76bf69cc53bacd2a45dac4215 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 17 Aug 2026 21:21:21 +0800 Subject: [PATCH 10/26] minor: skip iter excludes 'into_iter' method --- .../crates/ide-completion/src/completions/dot.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs index 774e14df48340..fb6a747a64803 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs @@ -118,6 +118,9 @@ pub(crate) fn complete_dot( ctx: dot_access.ctx, }; complete_methods(ctx, &iter, &traits_in_scope, |func| { + if func.name(ctx.db) == hir::sym::into_iter { + return; + } acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()), None) }); } @@ -1681,7 +1684,6 @@ fn foo() { expect![[r#" me into_iter() (as IntoIterator) fn(self) -> ::IntoIter me into_iter().by_ref() (as Iterator) fn(&mut self) -> &mut Self - me into_iter().into_iter() (as IntoIterator) fn(self) -> ::IntoIter me into_iter().next() (as Iterator) fn(&mut self) -> Option<::Item> me into_iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<::Item> "#]], @@ -1715,7 +1717,6 @@ fn foo() { me into_iter() (as IntoIterator) fn(self) -> ::IntoIter me iter() fn(&self) -> Iter me iter().by_ref() (as Iterator) fn(&mut self) -> &mut Self - me iter().into_iter() (as IntoIterator) fn(self) -> ::IntoIter me iter().next() (as Iterator) fn(&mut self) -> Option<::Item> me iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<::Item> "#]], From 8884e78f43a13bfbb6309bc929b1bcfb0086e461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 17 Aug 2026 16:19:18 +0300 Subject: [PATCH 11/26] Split VSIX publishing into different jobs and skip duplicates --- .../.github/workflows/release.yaml | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 70b96a64274b3..7d6e0199abe0b 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -199,15 +199,9 @@ jobs: publish: if: ${{ github.repository == 'rust-lang/rust-analyzer' || github.event_name == 'workflow_dispatch' }} - name: publish runs-on: ubuntu-latest needs: ["dist", "dist-x86_64-unknown-linux-musl"] steps: - - name: Install Nodejs - uses: actions/setup-node@v6 - with: - node-version: 22 - - name: Checkout repository uses: actions/checkout@v6 with: @@ -248,28 +242,41 @@ jobs: name: ${{ env.TAG }} token: ${{ secrets.GITHUB_TOKEN }} - - run: npm ci - working-directory: ./editors/code + publish-extension: + if: ${{ github.repository == 'rust-lang/rust-analyzer' || github.event_name == 'workflow_dispatch' }} + name: publish-extension (${{ matrix.cmd }}) + runs-on: ubuntu-latest + needs: publish + strategy: + fail-fast: false + matrix: + include: + - cmd: vsce + pat: MARKETPLACE_TOKEN + - cmd: ovsx + pat: OPENVSX_TOKEN + steps: + - name: Install Nodejs + uses: actions/setup-node@v6 + with: + node-version: 22 - - name: Publish Extension (Code Marketplace, release) - if: github.ref == 'refs/heads/release' && github.repository == 'rust-lang/rust-analyzer' - working-directory: ./editors/code - # token from https://dev.azure.com/rust-analyzer/ - run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: ${{ env.FETCH_DEPTH }} - - name: Publish Extension (OpenVSX, release) - if: github.ref == 'refs/heads/release' && github.repository == 'rust-lang/rust-analyzer' - working-directory: ./editors/code - run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix - timeout-minutes: 2 + - uses: actions/download-artifact@v8 + with: + pattern: dist-* + path: dist + merge-multiple: true - - name: Publish Extension (Code Marketplace, nightly) - if: github.ref != 'refs/heads/release' && github.repository == 'rust-lang/rust-analyzer' + - run: npm ci working-directory: ./editors/code - run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release - - name: Publish Extension (OpenVSX, nightly) - if: github.ref != 'refs/heads/release' && github.repository == 'rust-lang/rust-analyzer' + - name: Publish Extension + if: github.repository == 'rust-lang/rust-analyzer' working-directory: ./editors/code - run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix + run: npx ${{ matrix.cmd }} publish --skip-duplicate --pat ${{ secrets[matrix.pat] }} --packagePath ../../dist/rust-analyzer-*.vsix ${{ github.ref != 'refs/heads/release' && '--pre-release' || '' }} timeout-minutes: 2 From 778fc4d9e3e9b3c64a9dbee5c75c92364cd73cd2 Mon Sep 17 00:00:00 2001 From: Parman Mohammadalizadeh Date: Tue, 18 Aug 2026 22:06:11 +0200 Subject: [PATCH 12/26] fix: allow `asm!` label blocks to diverge Label operands were inferred with `infer_expr`, which demands the block's type be equal to `()`. A block that diverges has type `!`, so code like `label { break; }` inside a loop reported a false `expected (), found !`. Follow rustc's handling in `check_expr_asm` and only demand a supertype when the block does not diverge, saving and restoring `diverges` around it. --- .../crates/hir-ty/src/infer/expr.rs | 12 +++++++++-- .../crates/hir-ty/src/tests/simple.rs | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 0446978dd996a..f247b517c541f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -821,7 +821,7 @@ impl<'db> InferenceContext<'db> { } }; - let diverge = asm.options.contains(AsmOptions::NORETURN); + let mut diverge = asm.options.contains(AsmOptions::NORETURN); asm.operands.iter().for_each(|(_, operand)| match *operand { AsmOperand::In { expr, .. } => check_expr_asm_operand(self, expr, true), AsmOperand::Out { expr: Some(expr), .. } | AsmOperand::InOut { expr, .. } => { @@ -835,11 +835,19 @@ impl<'db> InferenceContext<'db> { } } AsmOperand::Label(expr) => { - self.infer_expr( + let previous_diverges = self.diverges; + // The label blocks should have unit return value or diverge. + let ty = self.infer_expr_inner( expr, &Expectation::HasType(self.types.types.unit), ExprIsRead::No, ); + if !ty.is_never() { + _ = self.demand_suptype(expr.into(), self.types.types.unit, ty); + diverge = false; + } + // We need this to avoid false unreachable warning when a label diverges. + self.diverges = previous_diverges; } AsmOperand::Const(expr) => { self.infer_expr(expr, &Expectation::None, ExprIsRead::No); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 3cdfe4edcb908..97921e8ab92d5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -4162,6 +4162,27 @@ extern "C" fn foo() -> ! { ); } +#[test] +fn asm_label_can_diverge() { + check_no_mismatches( + r#" +//- minicore: asm +fn foo() { + loop { + unsafe { + core::arch::asm!( + "/* {} */", + label { + break; + } + ); + } + } +} + "#, + ); +} + #[test] fn regression_21478() { check_infer( From 158e13deb3c0a923e64ff30bbaa19f25d2d5a436 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 7 Aug 2026 12:00:29 +0200 Subject: [PATCH 13/26] Remove `From for T` *reservation* impl --- library/core/src/convert/mod.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index 912623b73050e..8bd2ea302b605 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -793,21 +793,6 @@ const impl From for T { } } -/// **Stability note:** This impl does not yet exist, but we are -/// "reserving space" to add it in the future. See -/// [rust-lang/rust#64715][#64715] for details. -/// -/// [#64715]: https://github.com/rust-lang/rust/issues/64715 -#[stable(feature = "convert_infallible", since = "1.34.0")] -#[rustc_reservation_impl = "permitting this impl would forbid us from adding \ - `impl From for T` later; see rust-lang/rust#64715 for details"] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From for T { - fn from(t: !) -> T { - t - } -} - // TryFrom implies TryInto #[stable(feature = "try_from", since = "1.34.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] From 7c696e540ae20af639cfb926184d707da8092131 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 7 Aug 2026 16:52:06 +0200 Subject: [PATCH 14/26] bless ui tests --- ...erved.rs => never-from-impl-is-not-reserved.rs} | 7 ++++--- .../never-from-impl-is-reserved.current.stderr | 14 -------------- .../never-from-impl-is-reserved.next.stderr | 14 -------------- 3 files changed, 4 insertions(+), 31 deletions(-) rename tests/ui/never_type/{never-from-impl-is-reserved.rs => never-from-impl-is-not-reserved.rs} (53%) delete mode 100644 tests/ui/never_type/never-from-impl-is-reserved.current.stderr delete mode 100644 tests/ui/never_type/never-from-impl-is-reserved.next.stderr diff --git a/tests/ui/never_type/never-from-impl-is-reserved.rs b/tests/ui/never_type/never-from-impl-is-not-reserved.rs similarity index 53% rename from tests/ui/never_type/never-from-impl-is-reserved.rs rename to tests/ui/never_type/never-from-impl-is-not-reserved.rs index c673462f2962a..22b1c14db0bdf 100644 --- a/tests/ui/never_type/never-from-impl-is-reserved.rs +++ b/tests/ui/never_type/never-from-impl-is-not-reserved.rs @@ -1,5 +1,7 @@ -// check that the `for T: From` impl is reserved +// check that the `for T: From` impl is not reserved anymore +//@ check-pass +// //@ revisions: current next //@ ignore-compare-mode-next-solver (explicit revisions) //@[next] compile-flags: -Znext-solver=coherence @@ -10,7 +12,6 @@ pub struct MyFoo; pub trait MyTrait {} impl MyTrait for MyFoo {} -// This will conflict with the first impl if we impl `for T: From`. -impl MyTrait for T where T: From {} //~ ERROR conflicting implementation +impl MyTrait for T where T: From {} fn main() {} diff --git a/tests/ui/never_type/never-from-impl-is-reserved.current.stderr b/tests/ui/never_type/never-from-impl-is-reserved.current.stderr deleted file mode 100644 index 7868206950c34..0000000000000 --- a/tests/ui/never_type/never-from-impl-is-reserved.current.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[E0119]: conflicting implementations of trait `MyTrait` for type `MyFoo` - --> $DIR/never-from-impl-is-reserved.rs:14:1 - | -LL | impl MyTrait for MyFoo {} - | ---------------------- first implementation here -LL | // This will conflict with the first impl if we impl `for T: From`. -LL | impl MyTrait for T where T: From {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyFoo` - | - = note: permitting this impl would forbid us from adding `impl From for T` later; see rust-lang/rust#64715 for details - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0119`. diff --git a/tests/ui/never_type/never-from-impl-is-reserved.next.stderr b/tests/ui/never_type/never-from-impl-is-reserved.next.stderr deleted file mode 100644 index 7868206950c34..0000000000000 --- a/tests/ui/never_type/never-from-impl-is-reserved.next.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[E0119]: conflicting implementations of trait `MyTrait` for type `MyFoo` - --> $DIR/never-from-impl-is-reserved.rs:14:1 - | -LL | impl MyTrait for MyFoo {} - | ---------------------- first implementation here -LL | // This will conflict with the first impl if we impl `for T: From`. -LL | impl MyTrait for T where T: From {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyFoo` - | - = note: permitting this impl would forbid us from adding `impl From for T` later; see rust-lang/rust#64715 for details - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0119`. From 88f14afc99eacc923455b679dee2bf2da52879a8 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Thu, 20 Aug 2026 04:20:54 +0000 Subject: [PATCH 15/26] Prepare for merging from rust-lang/rust This updates the rust-version file to f7d782a3be46d6bb4b9792fe69a61db389ba1769. --- 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 2f175e966812d..9ff8b0c27d19c 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -2c39ff499469be916d4e45506d1afed69bbaddb7 +f7d782a3be46d6bb4b9792fe69a61db389ba1769 From 0b8f0072488d6504a8148d7a8791f159ceae9589 Mon Sep 17 00:00:00 2001 From: YUZHEthefool <2804776511@qq.com> Date: Thu, 20 Aug 2026 22:16:44 +0800 Subject: [PATCH 16/26] fix:prevent stack overflow for recursive ADT layouts --- .../crates/hir-ty/src/layout/adt.rs | 4 ++++ .../crates/hir-ty/src/layout/tests.rs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index 47a960e300cc7..2ba6408c45f2c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -16,6 +16,7 @@ use crate::{ db::HirDatabase, layout::{Layout, LayoutCx, LayoutError, field_ty}, next_solver::StoredGenericArgs, + representability::{Representability, representability}, traits::StoredParamEnvAndCrate, }; @@ -30,6 +31,9 @@ pub fn layout_of_adt_query( let Ok(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable); }; + if representability(db, def) == Representability::Infinite { + return Err(LayoutError::RecursiveTypeWithoutIndirection); + } let dl = target; let cx = LayoutCx::new(dl); let handle_variant = |def: VariantId, var: &VariantFields| { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index 72befc58a1236..5098b38c4380c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -275,6 +275,13 @@ fn recursive() { struct BoxLike(*mut T); struct Goal(BoxLike); } + size_and_align! { + struct Foo { + x: *const Foo<[T; 1]>, + y: *const T, + } + struct Goal(Foo); + } check_fail(r#"struct Goal(Goal);"#, LayoutError::RecursiveTypeWithoutIndirection); check_fail( r#" @@ -283,6 +290,18 @@ fn recursive() { "#, LayoutError::RecursiveTypeWithoutIndirection, ); + check_fail( + r#" +struct Foo { + x: Foo<[T; 1]>, + y: T, +} +struct Goal { + x: Foo, +} +"#, + LayoutError::RecursiveTypeWithoutIndirection, + ); } #[test] From 31c34201b87f9b2071bb13e0f47dbddfe643ebc8 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 21 Aug 2026 02:22:39 +0300 Subject: [PATCH 17/26] Fix 1.98.0 Clippy and rustfmt --- .../rust-analyzer/crates/ide-ssr/src/tests.rs | 15 ++++-------- .../ide/src/annotations/fn_references.rs | 1 + .../src/legacy_protocol/msg/flat.rs | 23 ++++++++++--------- .../crates/profile/src/memory_usage.rs | 13 ++++------- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs index d8c15cabb2fb8..8d1858ccf2226 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs @@ -72,18 +72,13 @@ pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Ve let (db, file_id) = ide_db::RootDatabase::with_single_file(code); (db, file_id, RangeOrOffset::Offset(0.into())) }; - let selections; - let position; - match range_or_offset { + + let (position, selections) = match range_or_offset { RangeOrOffset::Range(range) => { - position = FilePosition { file_id, offset: range.start() }; - selections = vec![FileRange { file_id, range }]; - } - RangeOrOffset::Offset(offset) => { - position = FilePosition { file_id, offset }; - selections = vec![]; + (FilePosition { file_id, offset: range.start() }, vec![FileRange { file_id, range }]) } - } + RangeOrOffset::Offset(offset) => (FilePosition { file_id, offset }, vec![]), + }; let mut local_roots = FxHashSet::default(); local_roots.insert(WORKSPACE); LocalRoots::get(&db).set_roots(&mut db).to(local_roots); diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations/fn_references.rs b/src/tools/rust-analyzer/crates/ide/src/annotations/fn_references.rs index 427a2eff82017..b43a23e69f37b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations/fn_references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations/fn_references.rs @@ -87,6 +87,7 @@ mod tests { ); let refs = super::find_all_methods(&analysis.db, pos.file_id); + #[expect(clippy::single_range_in_vec_init, reason = "this is not a mistake")] check_result(&refs, &[28..=34]); } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs index ae03be9aa7a26..b9b6247b54fab 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs @@ -54,7 +54,7 @@ pub type SpanDataIndexMap = pub fn serialize_span_data_index_map(map: &SpanDataIndexMap) -> Vec { map.iter() - .flat_map(|span| { + .map(|span| { [ span.anchor.file_id.as_u32(), span.anchor.ast_id.into_raw(), @@ -63,14 +63,16 @@ pub fn serialize_span_data_index_map(map: &SpanDataIndexMap) -> Vec { span.ctx.into_u32(), ] }) - .collect() + .collect::>() + .into_flattened() } pub fn deserialize_span_data_index_map(map: &[u32]) -> SpanDataIndexMap { - debug_assert!(map.len().is_multiple_of(5)); - map.chunks_exact(5) - .map(|span| { - let &[file_id, ast_id, start, end, e] = span else { unreachable!() }; + let (chunks, remainder) = map.as_chunks(); + assert!(remainder.is_empty()); + chunks + .iter() + .map(|&[file_id, ast_id, start, end, e]| { Span { anchor: SpanAnchor { file_id: EditionedFileId::from_raw(file_id), @@ -345,14 +347,13 @@ impl FlatTree { } fn read_vec T, const N: usize>(xs: Vec, f: F) -> Vec { - let mut chunks = xs.chunks_exact(N); - let res = chunks.by_ref().map(|chunk| f(chunk.try_into().unwrap())).collect(); - assert!(chunks.remainder().is_empty()); - res + let (chunks, remainder) = xs.as_chunks(); + assert!(remainder.is_empty()); + chunks.iter().map(|chunk| f(*chunk)).collect() } fn write_vec [u32; N], const N: usize>(xs: Vec, f: F) -> Vec { - xs.into_iter().flat_map(f).collect() + xs.into_iter().map(f).collect::>().into_flattened() } impl SubtreeRepr { diff --git a/src/tools/rust-analyzer/crates/profile/src/memory_usage.rs b/src/tools/rust-analyzer/crates/profile/src/memory_usage.rs index a8a409bd4686d..072a01c8298ec 100644 --- a/src/tools/rust-analyzer/crates/profile/src/memory_usage.rs +++ b/src/tools/rust-analyzer/crates/profile/src/memory_usage.rs @@ -30,28 +30,25 @@ impl MemoryUsage { allocated: Bytes(jemalloc_ctl::stats::allocated::read().unwrap() as isize), } } - all(target_os = "linux", target_env = "gnu") => { - memusage_linux() - } + all(target_os = "linux", target_env = "gnu") => memusage_linux(), windows => { // There doesn't seem to be an API for determining heap usage, so we try to // approximate that by using the Commit Charge value. - use windows_sys::Win32::System::{Threading::*, ProcessStatus::*}; use std::mem::MaybeUninit; + use windows_sys::Win32::System::{ProcessStatus::*, Threading::*}; let proc = unsafe { GetCurrentProcess() }; let mut mem_counters = MaybeUninit::uninit(); let cb = size_of::(); - let ret = unsafe { GetProcessMemoryInfo(proc, mem_counters.as_mut_ptr(), cb as u32) }; + let ret = + unsafe { GetProcessMemoryInfo(proc, mem_counters.as_mut_ptr(), cb as u32) }; assert!(ret != 0); let usage = unsafe { mem_counters.assume_init().PagefileUsage }; MemoryUsage { allocated: Bytes(usage as isize) } } - _ => { - MemoryUsage { allocated: Bytes(0) } - } + _ => MemoryUsage { allocated: Bytes(0) }, } } } From cef9fe6d0825eb060823d1835d42dd2cf1bb8c82 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 21 Aug 2026 13:24:13 +0300 Subject: [PATCH 18/26] Remove two unused public methods I forgot to change this when I changed https://github.com/rust-lang/rust-analyzer/pull/23079. --- src/tools/rust-analyzer/crates/intern/src/symbol.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol.rs b/src/tools/rust-analyzer/crates/intern/src/symbol.rs index cf41db85a163d..91fc11fa0dae9 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol.rs @@ -174,19 +174,6 @@ impl Symbol { self.repr.as_str() } - #[inline] - pub fn into_raw(self) -> NonNull<*const str> { - ManuallyDrop::new(self).repr.packed - } - - /// # Safety - /// - /// The pointer must have come from [`Symbol::into_raw()`]. - #[inline] - pub unsafe fn from_raw(ptr: NonNull<*const str>) -> Symbol { - Symbol { repr: TaggedArcPtr { packed: ptr } } - } - #[inline] fn select_shard( storage: &'static Map, From d5d5ff7edfcc3885c38b5d76c19c641e001f78fe Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 29 Jul 2026 11:10:21 +0200 Subject: [PATCH 19/26] make `pad_i32` of `PassMode::cast` an integer so that we can specify more than one i32 of padding. --- .../src/abi/pass_mode.rs | 4 +-- compiler/rustc_codegen_gcc/src/abi.rs | 12 ++++---- compiler/rustc_codegen_gcc/src/type_of.rs | 4 +-- compiler/rustc_codegen_llvm/src/abi.rs | 28 ++++++++++--------- compiler/rustc_codegen_ssa/src/mir/block.rs | 9 +++--- compiler/rustc_codegen_ssa/src/mir/mod.rs | 8 +++--- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 4 +-- .../src/mono_checks/abi_check.rs | 2 +- compiler/rustc_public/src/abi.rs | 2 +- .../src/unstable/convert/stable/abi.rs | 4 +-- compiler/rustc_target/src/callconv/mips.rs | 2 +- compiler/rustc_target/src/callconv/mips64.rs | 2 +- compiler/rustc_target/src/callconv/mod.rs | 17 +++++------ compiler/rustc_target/src/callconv/sparc.rs | 2 +- compiler/rustc_target/src/callconv/sparc64.rs | 2 +- tests/ui/abi/pass-indirectly-attr.stderr | 2 +- 16 files changed, 55 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs index c4d4ddcf6b753..1c552ca1a9c32 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs @@ -122,8 +122,8 @@ impl<'tcx> ArgAbiExt<'tcx> for ArgAbi<'tcx, Ty<'tcx>> { } _ => unreachable!("{:?}", self.layout.backend_repr), }, - PassMode::Cast { ref cast, pad_i32 } => { - assert!(!pad_i32, "padding support not yet implemented"); + PassMode::Cast { ref cast, pad_i32_count } => { + assert_eq!(pad_i32_count, 0, "padding support not yet implemented"); cast_target_to_abi_params(cast).into_iter().map(|(_, param)| param).collect() } PassMode::Indirect { attrs, meta_attrs: None, on_stack } => { diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs index 1b7bb8c907735..2901eb8b1a6d2 100644 --- a/compiler/rustc_codegen_gcc/src/abi.rs +++ b/compiler/rustc_codegen_gcc/src/abi.rs @@ -168,11 +168,13 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { )); continue; } - PassMode::Cast { ref cast, pad_i32 } => { - // add padding - if pad_i32 { - argument_tys.push(Reg::i32().gcc_type(cx)); - } + PassMode::Cast { ref cast, pad_i32_count } => { + // Add padding. + argument_tys.extend(std::iter::repeat_n( + Reg::i32().gcc_type(cx), + usize::from(pad_i32_count), + )); + let ty = cast.gcc_type(cx); apply_attrs(ty, &cast.attrs, argument_tys.len()) } diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs index c6c32236ab49f..53192c0a087e4 100644 --- a/compiler/rustc_codegen_gcc/src/type_of.rs +++ b/compiler/rustc_codegen_gcc/src/type_of.rs @@ -346,8 +346,8 @@ impl<'gcc, 'tcx> LayoutTypeCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn_abi.ptr_to_gcc_type(self) } - fn reg_backend_type(&self, _ty: &Reg) -> Type<'gcc> { - unimplemented!(); + fn reg_backend_type(&self, ty: &Reg) -> Type<'gcc> { + ty.gcc_type(self) } fn fn_decl_backend_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Type<'gcc> { diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 65bb32ee666f2..816ebe3fcf3d9 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -249,7 +249,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> { PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { bug!("unsized `ArgAbi` cannot be stored"); } - PassMode::Cast { cast, pad_i32: _ } => { + PassMode::Cast { cast, pad_i32_count: _ } => { // The ABI mandates that the value is passed as a different struct representation. // Spill and reload it from the stack to convert from the ABI representation to // the Rust representation. @@ -366,7 +366,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { let llreturn_ty = match &self.ret.mode { PassMode::Ignore => cx.type_void(), PassMode::Direct(_) | PassMode::Pair(..) => self.ret.layout.immediate_llvm_type(cx), - PassMode::Cast { cast, pad_i32: _ } => cast.llvm_type(cx), + PassMode::Cast { cast, pad_i32_count: _ } => cast.llvm_type(cx), PassMode::Indirect { .. } => { llargument_tys.push(cx.type_ptr()); cx.type_void() @@ -405,11 +405,13 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { continue; } PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => cx.type_ptr(), - PassMode::Cast { cast, pad_i32 } => { - // add padding - if *pad_i32 { - llargument_tys.push(Reg::i32().llvm_type(cx)); - } + PassMode::Cast { cast, pad_i32_count } => { + // Add padding. + llargument_tys.extend(std::iter::repeat_n( + Reg::i32().llvm_type(cx), + usize::from(*pad_i32_count), + )); + // Compute the LLVM type we use for this function from the cast type. // We assume here that ABI-compatible Rust types have the same cast type. cast.llvm_type(cx) @@ -511,7 +513,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { ); } } - PassMode::Cast { cast, pad_i32: _ } => { + PassMode::Cast { cast, pad_i32_count: _ } => { cast.attrs.apply_attrs_to_llfn(llvm::AttributePlace::ReturnValue, cx, llfn); } _ => {} @@ -580,8 +582,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { apply_range_attr(llvm::AttributePlace::Argument(ii), scalar_b); } } - PassMode::Cast { cast, pad_i32 } => { - if *pad_i32 { + PassMode::Cast { cast, pad_i32_count } => { + for _ in 0..*pad_i32_count { apply(&ArgAttributes::new()); } apply(&cast.attrs); @@ -630,7 +632,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { ); attributes::apply_to_callsite(callsite, llvm::AttributePlace::Argument(i), &[sret]); } - PassMode::Cast { cast, pad_i32: _ } => { + PassMode::Cast { cast, pad_i32_count: _ } => { cast.attrs.apply_attrs_to_callsite( llvm::AttributePlace::ReturnValue, bx.cx, @@ -666,8 +668,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { apply(bx.cx, a); apply(bx.cx, b); } - PassMode::Cast { cast, pad_i32 } => { - if *pad_i32 { + PassMode::Cast { cast, pad_i32_count } => { + for _ in 0..*pad_i32_count { apply(bx.cx, &ArgAttributes::new()); } apply(bx.cx, &cast.attrs); diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 7f907bc630b2f..afd9a88784c2f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -588,7 +588,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } - PassMode::Cast { cast: cast_ty, pad_i32: _ } => { + PassMode::Cast { cast: cast_ty, pad_i32_count: _ } => { let op = match self.locals[mir::RETURN_PLACE] { LocalRef::Operand(op) => op, LocalRef::PendingOperand => bug!("use of return before def"), @@ -1936,9 +1936,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ) { match arg.mode { PassMode::Ignore => return, - PassMode::Cast { pad_i32: true, .. } => { + PassMode::Cast { pad_i32_count, .. } => { // Fill padding with undef value, where applicable. - llargs.push(bx.const_undef(bx.reg_backend_type(&Reg::i32()))); + let undef = bx.const_undef(bx.reg_backend_type(&Reg::i32())); + llargs.extend(std::iter::repeat_n(undef, usize::from(pad_i32_count))); } PassMode::Pair(..) => match op.val { Pair(a, b) => { @@ -2025,7 +2026,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { if by_ref && !arg.is_indirect() { // Have to load the argument, maybe while casting it. - if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode { + if let PassMode::Cast { cast, pad_i32_count: _ } = &arg.mode { // The ABI mandates that the value is passed as a different struct representation. // Spill and reload it from the stack to convert from the Rust representation to // the ABI representation. diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index f8f4f09f75825..6e87a295e9d2b 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -501,8 +501,8 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( for i in 0..tupled_arg_tys.len() { let arg = &fx.fn_abi.args[idx]; idx += 1; - if let PassMode::Cast { pad_i32: true, .. } = arg.mode { - llarg_idx += 1; + if let PassMode::Cast { pad_i32_count, .. } = arg.mode { + llarg_idx += usize::from(pad_i32_count); } let pr_field = place.project_field(bx, i); bx.store_fn_arg(arg, &mut llarg_idx, pr_field); @@ -529,8 +529,8 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let arg = &fx.fn_abi.args[idx]; idx += 1; - if let PassMode::Cast { pad_i32: true, .. } = arg.mode { - llarg_idx += 1; + if let PassMode::Cast { pad_i32_count, .. } = arg.mode { + llarg_idx += usize::from(pad_i32_count); } if !memory_locals.contains(local) { diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 33cc321ea6d32..05b87bb6d7159 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -472,9 +472,9 @@ fn wasm_type<'tcx>(signature: &mut String, arg_abi: &ArgAbi<'_, Ty<'tcx>>, ptr_t } other => unreachable!("{other:?}"), }, - PassMode::Cast { pad_i32, ref cast } => { + PassMode::Cast { pad_i32_count, ref cast } => { // For wasm, Cast is used for single-field primitive wrappers like `struct Wrapper(i64);` - assert!(!pad_i32, "not currently used by wasm calling convention"); + assert_eq!(pad_i32_count, 0, "not currently used by wasm calling convention"); assert!(cast.prefix.is_empty(), "no prefix"); assert_eq!(cast.rest.total, arg_abi.layout.size, "single item"); diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 5f44e2e288821..e6c278bd8ce7b 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -24,7 +24,7 @@ enum UsesVectorRegisters { fn passes_vectors_by_value(mode: &PassMode, repr: &BackendRepr) -> UsesVectorRegisters { match mode { PassMode::Ignore | PassMode::Indirect { .. } => UsesVectorRegisters::No, - PassMode::Cast { pad_i32: _, cast } + PassMode::Cast { pad_i32_count: _, cast } if cast.prefix.iter().any(|x| matches!(x.kind, RegKind::Vector { .. })) || matches!(cast.rest.unit.kind, RegKind::Vector { .. }) => { diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index 02674e4107c77..910f4a5745a7d 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -55,7 +55,7 @@ pub enum PassMode { /// The argument has a layout abi of `ScalarPair`. Pair(Opaque, Opaque), /// Pass the argument after casting it. - Cast { pad_i32: bool, cast: Opaque }, + Cast { pad_i32_count: u8, cast: Opaque }, /// Pass the argument indirectly via a hidden pointer. Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, } diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index 31104ce897ffb..4bb00b4c04394 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -165,8 +165,8 @@ impl<'tcx> Stable<'tcx> for callconv::PassMode { callconv::PassMode::Pair(first, second) => { PassMode::Pair(opaque(first), opaque(second)) } - callconv::PassMode::Cast { pad_i32, cast } => { - PassMode::Cast { pad_i32: *pad_i32, cast: opaque(cast) } + callconv::PassMode::Cast { pad_i32_count, cast } => { + PassMode::Cast { pad_i32_count: *pad_i32_count, cast: opaque(cast) } } callconv::PassMode::Indirect { attrs, meta_attrs, on_stack } => PassMode::Indirect { attrs: opaque(attrs), diff --git a/compiler/rustc_target/src/callconv/mips.rs b/compiler/rustc_target/src/callconv/mips.rs index d2572cc035c1c..d3a39d05b4964 100644 --- a/compiler/rustc_target/src/callconv/mips.rs +++ b/compiler/rustc_target/src/callconv/mips.rs @@ -35,7 +35,7 @@ where let size = arg.layout.size; if arg.layout.is_aggregate() { - let pad_i32 = !offset.is_aligned(align); + let pad_i32 = u8::from(!offset.is_aligned(align)); arg.cast_to_and_pad_i32(Uniform::new(Reg::i32(), size), pad_i32); } else { arg.extend_integer_width_to(32); diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index a9d5ec958889f..8002f98507ba8 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -96,7 +96,7 @@ where // Detect need for padding let align = Ord::clamp(arg.layout.align.abi, dl.i64_align, dl.i128_align); - let pad_i32 = !offset.is_aligned(align); + let pad_i32 = u8::from(!offset.is_aligned(align)); if !arg.layout.is_aggregate() { extend_integer_width_mips(arg, 64); diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index a06a6a0a69e12..26fedbd8a5481 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -55,8 +55,9 @@ pub enum PassMode { Pair(ArgAttributes, ArgAttributes), /// Pass the argument after casting it. See the `CastTarget` docs for details. /// - /// `pad_i32` indicates if a `Reg::i32()` dummy argument is emitted before the real argument. - Cast { pad_i32: bool, cast: Box }, + /// `pad_i32` indicates how many `Reg::i32()` dummy arguments are emitted before the real + /// argument. + Cast { pad_i32_count: u8, cast: Box }, /// Pass the argument indirectly via a hidden pointer. /// /// The `meta_attrs` value, if any, is for the metadata (vtable or length) of an unsized @@ -84,8 +85,8 @@ impl PassMode { (PassMode::Direct(a1), PassMode::Direct(a2)) => a1.eq_abi(a2), (PassMode::Pair(a1, b1), PassMode::Pair(a2, b2)) => a1.eq_abi(a2) && b1.eq_abi(b2), ( - PassMode::Cast { cast: c1, pad_i32: pad1 }, - PassMode::Cast { cast: c2, pad_i32: pad2 }, + PassMode::Cast { cast: c1, pad_i32_count: pad1 }, + PassMode::Cast { cast: c2, pad_i32_count: pad2 }, ) => c1.eq_abi(c2) && pad1 == pad2, ( PassMode::Indirect { attrs: a1, meta_attrs: None, on_stack: s1 }, @@ -507,12 +508,12 @@ impl<'a, Ty> ArgAbi<'a, Ty> { } pub fn cast_to>(&mut self, target: T) { - self.mode = PassMode::Cast { cast: Box::new(target.into()), pad_i32: false }; + self.mode = PassMode::Cast { cast: Box::new(target.into()), pad_i32_count: 0 }; } pub fn cast_to_with_attrs>(&mut self, target: T, attrs: ArgAttributes) { self.mode = - PassMode::Cast { cast: Box::new(target.into().with_attrs(attrs)), pad_i32: false }; + PassMode::Cast { cast: Box::new(target.into().with_attrs(attrs)), pad_i32_count: 0 }; } /// Cast to `target`, forwarding `NoUndef` only when the layout provably has no uninit @@ -535,8 +536,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> { self.cast_to_with_attrs(target, attr.into()); } - pub fn cast_to_and_pad_i32>(&mut self, target: T, pad_i32: bool) { - self.mode = PassMode::Cast { cast: Box::new(target.into()), pad_i32 }; + pub fn cast_to_and_pad_i32>(&mut self, target: T, pad_i32_count: u8) { + self.mode = PassMode::Cast { cast: Box::new(target.into()), pad_i32_count }; } pub fn is_indirect(&self) -> bool { diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index d424214aa497e..71af508915e59 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -34,7 +34,7 @@ where let align = arg.layout.align.abi.max(dl.i32_align).min(dl.i64_align); if arg.layout.is_aggregate() { - let pad_i32 = !offset.is_aligned(align); + let pad_i32 = u8::from(!offset.is_aligned(align)); arg.cast_to_and_pad_i32(Uniform::new(Reg::i32(), size), pad_i32); } else { arg.extend_integer_width_to(32); diff --git a/compiler/rustc_target/src/callconv/sparc64.rs b/compiler/rustc_target/src/callconv/sparc64.rs index 6b19f8ebd76ce..7e441d7100c39 100644 --- a/compiler/rustc_target/src/callconv/sparc64.rs +++ b/compiler/rustc_target/src/callconv/sparc64.rs @@ -190,7 +190,7 @@ fn classify_arg<'a, Ty, C>( _ => CastTarget::prefixed(regs, Uniform::new(Reg::i8(), Size::ZERO)), }; - arg.cast_to_and_pad_i32(cast_target.with_attrs(attrs.into()), pad); + arg.cast_to_and_pad_i32(cast_target.with_attrs(attrs.into()), u8::from(pad)); } pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index 320840c8149f5..efeec0d86982b 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -121,7 +121,7 @@ error: fn_abi_of(extern_rust) = FnAbi { }, }, mode: Cast { - pad_i32: false, + pad_i32_count: 0, cast: CastTarget { prefix: [], rest_offset: None, From 555a43c67a0d62703498cb10d19d29f62e9d67de Mon Sep 17 00:00:00 2001 From: Riccardo Mazzarini Date: Fri, 21 Aug 2026 23:14:15 +0200 Subject: [PATCH 20/26] Use Cargo build directory for flycheck logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R-A currently stores flycheck’s captured stdout and stderr under Cargo’s `target_directory`. Cargo 1.91 stabilized `build.build-dir`, which allows intermediate build artifacts to be stored separately and exposes the resolved location as `build_directory` in cargo metadata (see https://github.com/rust-lang/cargo/pull/15833 and https://github.com/rust-lang/cargo/pull/15377). This PR changes the directory selection order for flycheck output to be `rust-analyzer.cargo.targetDir` → `build_directory` → `target_directory`. --- src/tools/rust-analyzer/Cargo.toml | 2 +- .../project-model/src/cargo_workspace.rs | 7 ++++ .../crates/rust-analyzer/src/flycheck.rs | 23 ++++++++----- .../crates/rust-analyzer/src/reload.rs | 9 +++-- .../tests/slow-tests/flycheck.rs | 34 +++++++++++++++++++ 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index d043a3aee4f7f..b798e364555df 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -104,7 +104,7 @@ lsp-server = { version = "0.7.9" } anyhow = "1.0.98" arrayvec = "0.7.6" bitflags = "2.9.1" -cargo_metadata = "0.23.0" +cargo_metadata = "0.23.1" camino = "1.2.2" crossbeam-channel = "0.5.15" dissimilar = "1.0.10" diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index a0a9815d8756f..9d2588380c22c 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -36,6 +36,7 @@ pub struct CargoWorkspace { targets: Arena, workspace_root: AbsPathBuf, target_directory: AbsPathBuf, + build_directory: Option, manifest_path: ManifestPath, is_virtual_workspace: bool, /// Whether this workspace represents the sysroot workspace. @@ -359,6 +360,7 @@ impl CargoWorkspace { let workspace_root = AbsPathBuf::assert(meta.workspace_root); let target_directory = AbsPathBuf::assert(meta.target_directory); + let build_directory = meta.build_directory.map(AbsPathBuf::assert); let mut is_virtual_workspace = true; let mut requires_rustc_private = false; @@ -517,6 +519,7 @@ impl CargoWorkspace { targets, workspace_root, target_directory, + build_directory, manifest_path: ws_manifest_path, is_virtual_workspace, requires_rustc_private, @@ -548,6 +551,10 @@ impl CargoWorkspace { &self.target_directory } + pub fn build_directory(&self) -> Option<&AbsPath> { + self.build_directory.as_deref() + } + pub fn package_flag(&self, package: &PackageData) -> String { if self.is_unique(&package.name) { package.name.clone() diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index 99e640a3cecb9..85edb239e3f57 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -226,6 +226,7 @@ impl FlycheckHandle { workspace_root: AbsPathBuf, manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, toolchain_version: Option, ) -> FlycheckHandle { let actor = FlycheckActor::new( @@ -238,6 +239,7 @@ impl FlycheckHandle { workspace_root, manifest_path, ws_target_dir, + ws_build_dir, toolchain_version, ); let (sender, receiver) = unbounded::(); @@ -431,6 +433,7 @@ struct FlycheckActor { manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, /// Either the workspace root of the workspace we are flychecking, /// or the project root of the project. root: Arc, @@ -533,6 +536,7 @@ impl FlycheckActor { workspace_root: AbsPathBuf, manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, toolchain_version: Option, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); @@ -547,6 +551,7 @@ impl FlycheckActor { scope: FlycheckScope::Workspace, manifest_path, ws_target_dir, + ws_build_dir, command_handle: None, command_receiver: None, diagnostics_cleared_for: Default::default(), @@ -633,14 +638,14 @@ impl FlycheckActor { sender, match &self.config { FlycheckConfig::Automatic { cargo_options, .. } => { - let ws_target_dir = - self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path); - let target_dir = - cargo_options.target_dir_config.target_dir(ws_target_dir); + let target_dir = cargo_options + .target_dir_config + .target_dir(self.ws_target_dir.as_deref()); - // If `"rust-analyzer.cargo.targetDir": null`, we should use - // workspace's target dir instead of hard-coded fallback. - let target_dir = target_dir.as_deref().or(ws_target_dir); + let output_dir = target_dir + .as_deref() + .or(self.ws_build_dir.as_deref()) + .or(self.ws_target_dir.as_deref()); Some( // As `CommandHandle::spawn`'s working directory is @@ -648,10 +653,10 @@ impl FlycheckActor { // from the flycheck's working directory, we should canonicalize // the output directory, otherwise we might write it into the // wrong target dir. - // If `target_dir` is an absolute path, it will replace + // If `output_dir` is an absolute path, it will replace // `self.root` and that's an intended behavior. self.root - .join(target_dir.unwrap_or( + .join(output_dir.unwrap_or( Utf8Path::new("target").join("rust-analyzer").as_path(), )) .join(format!("flycheck{}", self.id)) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 86c954b089e4d..039fbeff828e7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -902,6 +902,7 @@ impl GlobalState { None, None, None, + None, )] } crate::flycheck::InvocationStrategy::PerWorkspace => { @@ -921,6 +922,7 @@ impl GlobalState { cargo.workspace_root(), Some(cargo.manifest_path()), Some(cargo.target_directory()), + cargo.build_directory(), ), ProjectWorkspaceKind::Json(project) => { let config_json = crate::flycheck::FlycheckConfigJson { @@ -932,10 +934,10 @@ impl GlobalState { // in the workspace configuration. match config { _ if config_json.any_configured() => { - (config_json, project.path(), None, None) + (config_json, project.path(), None, None, None) } FlycheckConfig::CustomCommand { .. } => { - (config_json, project.path(), None, None) + (config_json, project.path(), None, None, None) } _ => return None, } @@ -949,7 +951,7 @@ impl GlobalState { .map( |( id, - (config_json, root, manifest_path, target_dir), + (config_json, root, manifest_path, target_dir, build_dir), sysroot_root, toolchain, )| { @@ -963,6 +965,7 @@ impl GlobalState { root.to_path_buf(), manifest_path.map(|it| it.to_path_buf()), target_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), + build_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), toolchain, ) }, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs index 7700643f03e75..327fa025f490d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs @@ -42,6 +42,40 @@ fn main() { ); } +#[test] +fn test_flycheck_output_uses_build_directory() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /.cargo/config.toml +[build] +build-dir = "build" + +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/main.rs +fn main() { + let x = 1; +} +"#, + ) + .with_config(serde_json::json!({ + "checkOnSave": true, + })) + .server() + .wait_until_workspace_is_loaded(); + + _ = server.wait_for_diagnostics(); + assert!(server.path().join("build/flycheck0/stdout").exists()); + assert!(server.path().join("build/flycheck0/stderr").exists()); +} + #[test] fn test_flycheck_diagnostic_cleared_after_fix() { if skip_slow_tests() { From 78da05d0bfc013a04807d71f3fde69b348a12ca8 Mon Sep 17 00:00:00 2001 From: Suryansh Dey Date: Sun, 23 Aug 2026 12:51:31 +0530 Subject: [PATCH 21/26] fix(hir): Use expression store of parent body if available --- .../crates/hir/src/source_analyzer.rs | 6 ++--- .../crates/ide/src/hover/tests.rs | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 209091683a01b..907193fe1d7ff 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -464,7 +464,7 @@ impl<'db> SourceAnalyzer<'db> { db, &self.resolver, self.store()?, - generic_def.into(), + self.resolver.expression_store_owner().unwrap_or_else(|| generic_def.into()), generic_def, &generics, // FIXME: Is this correct here? Anyway that should impact mostly diagnostics, which we don't emit here @@ -1890,7 +1890,7 @@ fn resolve_hir_path_<'db>( db, resolver, store?, - def.into(), + resolver.expression_store_owner().unwrap_or_else(|| def.into()), def, &generics, LifetimeElisionKind::Infer, @@ -2094,7 +2094,7 @@ fn resolve_hir_path_qualifier<'db>( db, resolver, store, - def.into(), + resolver.expression_store_owner().unwrap_or_else(|| def.into()), def, &generics, LifetimeElisionKind::Infer, diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 4fea4468c0725..fad322aa4f5da 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11948,3 +11948,25 @@ fn main() {} "#]], ); } + +#[test] +fn resolve_array_type_with_anon_const_panic() { + use syntax::AstNode; + let (analysis, position) = crate::fixture::position( + r#" +fn main() { + let x: [u8; 2 + 2$0] = [0; 4]; +} +"#, + ); + let db = &analysis.db; + hir::attach_db(db, || { + let sema = hir::Semantics::new(db); + let file = sema.parse_guess_edition(position.file_id); + let token = file.syntax().token_at_offset(position.offset).right_biased().unwrap(); + let type_node = token.parent_ancestors().find_map(syntax::ast::Type::cast).unwrap(); + + let resolved = sema.resolve_type(&type_node).unwrap(); + let _ = resolved.as_array(db); + }); +} From eea5f8c2f2c1f8c9f5ef1e9f462a821ea3f76f77 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 23 Aug 2026 18:21:09 +1000 Subject: [PATCH 22/26] Rename some `build: &Builder<'_>` to `builder` --- src/bootstrap/src/core/build_steps/format.rs | 65 ++++++++++---------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index d4dc9c2de53e6..4d16812127641 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -56,14 +56,14 @@ fn rustfmt( } } -fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, BuildStamp)> { - let stamp_file = BuildStamp::new(&build.out).with_prefix("rustfmt"); +fn get_rustfmt_version(builder: &Builder<'_>) -> Option<(String, BuildStamp)> { + let stamp_file = BuildStamp::new(&builder.out).with_prefix("rustfmt"); - let rustfmt = build.ensure(InternalRustfmt); + let rustfmt = builder.ensure(InternalRustfmt); let mut cmd = command(rustfmt.as_ref()?); cmd.arg("--version"); - let output = cmd.allow_failure().run_capture(build); + let output = cmd.allow_failure().run_capture(builder); if output.is_failure() { return None; } @@ -71,16 +71,16 @@ fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, BuildStamp)> { } /// Return whether the format cache can be reused. -fn verify_rustfmt_version(build: &Builder<'_>) -> bool { - let Some((version, stamp_file)) = get_rustfmt_version(build) else { +fn verify_rustfmt_version(builder: &Builder<'_>) -> bool { + let Some((version, stamp_file)) = get_rustfmt_version(builder) else { return false; }; stamp_file.add_stamp(version).is_up_to_date() } /// Updates the last rustfmt version used. -fn update_rustfmt_version(build: &Builder<'_>) { - let Some((version, stamp_file)) = get_rustfmt_version(build) else { +fn update_rustfmt_version(builder: &Builder<'_>) { + let Some((version, stamp_file)) = get_rustfmt_version(builder) else { return; }; @@ -91,16 +91,17 @@ fn update_rustfmt_version(build: &Builder<'_>) { /// Does not include removed files. /// /// Returns `None` if all files should be formatted. -fn get_modified_rs_files(build: &Builder<'_>) -> Result>, String> { +fn get_modified_rs_files(builder: &Builder<'_>) -> Result>, String> { // In CI `get_git_modified_files` returns something different to normal environment. // This shouldn't be called in CI anyway. - assert!(!build.config.is_running_on_ci()); + assert!(!builder.config.is_running_on_ci()); - if !verify_rustfmt_version(build) { + if !verify_rustfmt_version(builder) { return Ok(None); } - get_git_modified_files(&build.config.git_config(), Some(&build.config.src), &["rs"]).map(Some) + get_git_modified_files(&builder.config.git_config(), Some(&builder.config.src), &["rs"]) + .map(Some) } /// Rustfmt set via the config, or downloaded from CI, used to format local Rust code. @@ -143,13 +144,13 @@ fn print_paths(verb: &str, adjective: Option<&str>, paths: &[String]) { } pub fn format( - build: &Builder<'_>, + builder: &Builder<'_>, rustfmt_path: PathBuf, check: bool, all: bool, paths: &[PathBuf], ) { - if build.kind == Kind::Format && build.top_stage != 0 { + if builder.kind == Kind::Format && builder.top_stage != 0 { eprintln!("ERROR: `x fmt` only supports stage 0."); eprintln!("HELP: Use `x run rustfmt` to run in-tree rustfmt."); helpers::exit_process(1); @@ -161,7 +162,7 @@ pub fn format( ); helpers::exit_process(1); }; - if build.config.dry_run() { + if builder.config.dry_run() { return; } @@ -169,13 +170,15 @@ pub fn format( // `--all` is specified or we are in CI. We check all files in CI to avoid bugs in // `get_modified_rs_files` letting regressions slip through; we also care about CI time less // since this is still very fast compared to building the compiler. - let all = all || build.config.is_running_on_ci(); + let all = all || builder.config.is_running_on_ci(); - let mut builder = ignore::types::TypesBuilder::new(); - builder.add_defaults(); - builder.select("rust"); - let matcher = builder.build().unwrap(); - let rustfmt_config = build.src.join("rustfmt.toml"); + let matcher = { + let mut types = ignore::types::TypesBuilder::new(); + types.add_defaults(); + types.select("rust"); + types.build().unwrap() + }; + let rustfmt_config = builder.src.join("rustfmt.toml"); if !rustfmt_config.exists() { eprintln!("fmt error: Not running formatting checks; rustfmt.toml does not exist."); eprintln!("fmt error: This may happen in distributed tarballs."); @@ -183,7 +186,7 @@ pub fn format( } let rustfmt_config = t!(std::fs::read_to_string(&rustfmt_config)); let rustfmt_config: RustfmtConfig = t!(toml::from_str(&rustfmt_config)); - let mut override_builder = ignore::overrides::OverrideBuilder::new(&build.src); + let mut override_builder = ignore::overrides::OverrideBuilder::new(&builder.src); for ignore in rustfmt_config.ignore { if ignore.starts_with('!') { // A `!`-prefixed entry could be added as a whitelisted entry in `override_builder`, @@ -199,23 +202,23 @@ pub fn format( } } let git_available = - helpers::git(None).allow_failure().arg("--version").run_capture(build).is_success(); + helpers::git(None).allow_failure().arg("--version").run_capture(builder).is_success(); let mut adjective = None; if git_available { - let in_working_tree = helpers::git(Some(&build.src)) + let in_working_tree = helpers::git(Some(&builder.src)) .allow_failure() .arg("rev-parse") .arg("--is-inside-work-tree") - .run_capture(build) + .run_capture(builder) .is_success(); if in_working_tree { - let untracked_paths_output = helpers::git(Some(&build.src)) + let untracked_paths_output = helpers::git(Some(&builder.src)) .arg("status") .arg("--porcelain") .arg("-z") .arg("--untracked-files=normal") - .run_capture_stdout(build) + .run_capture_stdout(builder) .stdout(); let untracked_paths: Vec<_> = untracked_paths_output .split_terminator('\0') @@ -236,7 +239,7 @@ pub fn format( } if !all { adjective = Some("modified"); - match get_modified_rs_files(build) { + match get_modified_rs_files(builder) { Ok(Some(files)) => { if files.is_empty() { println!("fmt info: No modified files detected for formatting."); @@ -271,13 +274,13 @@ pub fn format( let override_ = override_builder.build().unwrap(); // `override` is a reserved keyword assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); - let src = build.src.clone(); + let src = builder.src.clone(); let (tx, rx): (SyncSender, _) = std::sync::mpsc::sync_channel(128); let walker = WalkBuilder::new(src.clone()).types(matcher).overrides(override_).build_parallel(); // There is a lot of blocking involved in spawning a child process and reading files to format. // Spawn more processes than available concurrency to keep the CPU busy. - let max_processes = build.jobs() as usize * 2; + let max_processes = builder.jobs() as usize * 2; // Spawn child processes on a separate thread so we can batch entries we have received from // ignore. @@ -370,5 +373,5 @@ pub fn format( // // NOTE: Because of the exit above, this is only reachable if formatting / format checking // succeeded. So we are not committing the version if formatting was not good. - update_rustfmt_version(build); + update_rustfmt_version(builder); } From 287084bf3061a74c8ad2ddc67e5f5810574fe7f3 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 23 Aug 2026 18:28:06 +1000 Subject: [PATCH 23/26] Rename `Build` to `Session` This renaming should at least make Session and Builder easier to distinguish. --- src/bootstrap/src/cli_main.rs | 12 +-- src/bootstrap/src/core/build_steps/check.rs | 2 +- src/bootstrap/src/core/build_steps/clean.rs | 36 +++---- src/bootstrap/src/core/build_steps/compile.rs | 6 +- src/bootstrap/src/core/build_steps/dist.rs | 16 ++-- src/bootstrap/src/core/build_steps/gcc.rs | 10 +- src/bootstrap/src/core/build_steps/llvm.rs | 2 +- src/bootstrap/src/core/build_steps/perf.rs | 4 +- src/bootstrap/src/core/build_steps/run.rs | 6 +- src/bootstrap/src/core/build_steps/setup.rs | 2 +- src/bootstrap/src/core/build_steps/test.rs | 18 ++-- src/bootstrap/src/core/build_steps/tool.rs | 6 +- src/bootstrap/src/core/build_steps/vendor.rs | 2 +- src/bootstrap/src/core/builder/cargo.rs | 26 +++--- .../src/core/builder/cli_paths/tests.rs | 8 +- src/bootstrap/src/core/builder/mod.rs | 47 +++++----- src/bootstrap/src/core/builder/tests.rs | 34 +++---- src/bootstrap/src/core/compiler.rs | 8 +- src/bootstrap/src/core/config/config.rs | 4 +- src/bootstrap/src/core/config/flags.rs | 6 +- src/bootstrap/src/core/metadata.rs | 26 +++--- src/bootstrap/src/core/sanity.rs | 93 +++++++++---------- src/bootstrap/src/core/session.rs | 69 +++++++------- src/bootstrap/src/utils/cc_detect.rs | 85 +++++++++-------- src/bootstrap/src/utils/cc_detect/tests.rs | 54 +++++------ src/bootstrap/src/utils/channel.rs | 6 +- src/bootstrap/src/utils/job.rs | 15 ++- src/bootstrap/src/utils/metrics.rs | 8 +- 28 files changed, 301 insertions(+), 310 deletions(-) diff --git a/src/bootstrap/src/cli_main.rs b/src/bootstrap/src/cli_main.rs index 8a74b2e598283..7fd4d3b692d99 100644 --- a/src/bootstrap/src/cli_main.rs +++ b/src/bootstrap/src/cli_main.rs @@ -16,7 +16,7 @@ use std::{env, process}; use crate::core::builder::StepStack; use crate::core::config::flags::{Flags, Subcommand}; use crate::core::config::{ChangeId, Config}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::debug; use crate::utils::change_tracker::{ CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes, @@ -157,9 +157,9 @@ pub fn main() { t!(symlink_dir_inner(&tracing_dir, &latest_trace_dir)); } - debug!("creating new build based on config"); - let mut build = Build::new(config); - build.build(); + debug!("creating new session based on config"); + let mut sess = Session::new(config); + sess.build(); if suggest_setup { println!("WARNING: you have not made a `bootstrap.toml`"); @@ -213,8 +213,8 @@ pub fn main() { #[cfg(feature = "tracing")] { - build.report_summary(&tracing_dir.join("command-stats.txt"), _start_time); - build.report_step_graph(&tracing_dir); + sess.report_summary(&tracing_dir.join("command-stats.txt"), _start_time); + sess.report_step_graph(&tracing_dir); guard.copy_to_dir(&tracing_dir); eprintln!("Tracing/profiling output has been written to {}", latest_trace_dir.display()); } diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 4a75cdbb1562f..45d84d533fe14 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -647,7 +647,7 @@ impl CommandLineStep for GccCodegenBackend { fn run(self, builder: &Builder<'_>) { // FIXME: remove once https://github.com/rust-lang/rust/issues/112393 is resolved - if builder.build.config.vendor { + if builder.sess.config.vendor { println!("Skipping checking of `rustc_codegen_gcc` with vendoring enabled."); return; } diff --git a/src/bootstrap/src/core/build_steps/clean.rs b/src/bootstrap/src/core/build_steps/clean.rs index 23f12bbb63e72..1c96f5ccd00d8 100644 --- a/src/bootstrap/src/core/build_steps/clean.rs +++ b/src/bootstrap/src/core/build_steps/clean.rs @@ -14,7 +14,7 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::flags::Subcommand; -use crate::core::session::{Build, Mode}; +use crate::core::session::{Mode, Session}; use crate::utils::build_stamp::BuildStamp; use crate::utils::helpers::t; @@ -47,7 +47,7 @@ impl CommandLineStep for CleanAll { panic!("--all and --stage can't be used at the same time for `x clean`"); } - clean(builder.build, all, stage) + clean(builder.sess, all, stage) } } @@ -105,8 +105,8 @@ clean_crate_tree! { Std, Mode::Std, "sysroot"; } -fn clean(build: &Build, all: bool, stage: Option) { - if build.config.dry_run() { +fn clean(sess: &Session, all: bool, stage: Option) { + if sess.config.dry_run() { return; } @@ -114,23 +114,23 @@ fn clean(build: &Build, all: bool, stage: Option) { // Clean the entire build directory if all { - rm_rf(&build.out); + rm_rf(&sess.out); return; } // Clean the target stage artifacts if let Some(stage) = stage { - clean_specific_stage(build, stage); + clean_specific_stage(sess, stage); return; } // Follow the default behaviour - clean_default(build); + clean_default(sess); } -fn clean_specific_stage(build: &Build, stage: u32) { - for host in &build.hosts { - let entries = match build.out.join(host).read_dir() { +fn clean_specific_stage(sess: &Session, stage: u32) { + for host in &sess.hosts { + let entries = match sess.out.join(host).read_dir() { Ok(iter) => iter, Err(_) => continue, }; @@ -150,18 +150,18 @@ fn clean_specific_stage(build: &Build, stage: u32) { } } -fn clean_default(build: &Build) { - rm_rf(&build.out.join("tmp")); - rm_rf(&build.out.join("dist")); - rm_rf(&build.out.join("bootstrap").join(".last-warned-change-id")); - rm_rf(&build.out.join("bootstrap-shims-dump")); - rm_rf(BuildStamp::new(&build.out).with_prefix("rustfmt").path()); +fn clean_default(sess: &Session) { + rm_rf(&sess.out.join("tmp")); + rm_rf(&sess.out.join("dist")); + rm_rf(&sess.out.join("bootstrap").join(".last-warned-change-id")); + rm_rf(&sess.out.join("bootstrap-shims-dump")); + rm_rf(BuildStamp::new(&sess.out).with_prefix("rustfmt").path()); - let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect(); + let mut hosts: Vec<_> = sess.hosts.iter().map(|t| sess.out.join(t)).collect(); // After cross-compilation, artifacts of the host architecture (which may differ from build.host) // might not get removed. // Adding its path (linked one for easier accessibility) will solve this problem. - hosts.push(build.out.join("host")); + hosts.push(sess.out.join("host")); for host in hosts { let entries = match host.read_dir() { diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 9970c5b21056e..bb598d19c9fc9 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -777,7 +777,7 @@ impl Step for StdLink { }; let is_downloaded_beta_stage0 = builder - .build + .sess .config .initial_rustc .starts_with(builder.out.join(compiler.host).join("stage0/bin")); @@ -1147,7 +1147,7 @@ impl CommandLineStep for Rustc { cargo.arg("-p").arg(krate); } - if builder.build.config.enable_bolt_settings && build_compiler.stage == 1 { + if builder.sess.config.enable_bolt_settings && build_compiler.stage == 1 { // Relocations are required for BOLT to work. cargo.env("RUSTC_BOLT_LINK_FLAGS", "1"); } @@ -1256,7 +1256,7 @@ pub fn rustc_cargo( // us a faster startup time. However GNU ld < 2.40 will error if we try to link a shared object // with direct references to protected symbols, so for now we only use protected symbols if // linking with LLD is enabled. - if builder.build.config.bootstrap_override_lld.is_used() { + if builder.sess.config.bootstrap_override_lld.is_used() { cargo.rustflag("-Zdefault-visibility=protected"); } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 53e7746d220f5..2112ec090f0b6 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -664,7 +664,7 @@ impl CommandLineStep for Rustc { let page_src = file_entry.path(); let page_dst = man_dst.join(file_entry.file_name()); let src_text = t!(std::fs::read_to_string(&page_src)); - let version = builder.rust_info().version(builder.build, &builder.version); + let version = builder.rust_info().version(builder.sess, &builder.version); let new_text = src_text.replace("", &version); t!(std::fs::write(&page_dst, &new_text)); } @@ -774,7 +774,7 @@ impl Step for DebuggerScripts { cp_debugger_script("gdb_load_rust_pretty_printers.py"); cp_debugger_script("gdb_lookup.py"); cp_debugger_script("gdb_providers.py"); - if builder.build.unstable_features() { + if builder.sess.unstable_features() { cp_debugger_script("gdb_trim_paths.py"); } @@ -787,7 +787,7 @@ impl Step for DebuggerScripts { cp_debugger_script("lldb_lookup.py"); cp_debugger_script("lldb_providers.py"); - if builder.build.unstable_features() { + if builder.sess.unstable_features() { cp_debugger_script("lldb_trim_paths.py"); } } @@ -1624,7 +1624,7 @@ impl CommandLineStep for Miri { // This prevents miri from being built for "dist" or "install" // on the stable/beta channels. It is a nightly-only tool and should // not be included. - if !builder.build.unstable_features() { + if !builder.sess.unstable_features() { return None; } @@ -1681,7 +1681,7 @@ impl CommandLineStep for CraneliftCodegenBackend { // This prevents rustc_codegen_cranelift from being built for "dist" // or "install" on the stable/beta channels. It is not yet stable and // should not be included. - if !builder.build.unstable_features() { + if !builder.sess.unstable_features() { return None; } @@ -1755,7 +1755,7 @@ impl CommandLineStep for GccCodegenBackend { // This prevents rustc_codegen_gcc from being built for "dist" // or "install" on the stable/beta channels. It is not yet stable and // should not be included. - if !builder.build.unstable_features() { + if !builder.sess.unstable_features() { return None; } @@ -2837,7 +2837,7 @@ impl CommandLineStep for Enzyme { // This prevents Enzyme from being built for "dist" // or "install" on the stable/beta channels. It is not yet stable and // should not be included. - if !builder.build.unstable_features() { + if !builder.sess.unstable_features() { return None; } @@ -3232,7 +3232,7 @@ impl CommandLineStep for Gcc { // This prevents gcc from being built for "dist" // or "install" on the stable/beta channels. It is not yet stable and // should not be included. - if !builder.build.unstable_features() { + if !builder.sess.unstable_features() { return None; } diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs index d3540bd21b0e4..650921d7231fc 100644 --- a/src/bootstrap/src/core/build_steps/gcc.rs +++ b/src/bootstrap/src/core/build_steps/gcc.rs @@ -290,7 +290,7 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target_pair: GccTargetPair) // Target on which libgccjit.so will be executed. Here we will generate a dylib with // instructions for that target. let host = target_pair.host; - if builder.build.cc_tool(host).is_like_clang() || builder.build.cxx_tool(host).is_like_clang() { + if builder.sess.cc_tool(host).is_like_clang() || builder.sess.cxx_tool(host).is_like_clang() { panic!( "Attempting to build GCC using Clang, which is known to misbehave. Please use GCC as the host C/C++ compiler. " ); @@ -327,19 +327,19 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target_pair: GccTargetPair) .arg("--with-bugurl=https://github.com/rust-lang/gcc/") .arg(format!("--prefix={}", install_dir.display())); - let cc = builder.build.cc(host).display().to_string(); + let cc = builder.sess.cc(host).display().to_string(); let cc = builder - .build + .sess .config .ccache .as_ref() .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}")); configure_cmd.env("CC", cc); - if let Ok(ref cxx) = builder.build.cxx(host) { + if let Ok(ref cxx) = builder.sess.cxx(host) { let cxx = cxx.display().to_string(); let cxx = builder - .build + .sess .config .ccache .as_ref() diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 94c886649109f..378bbae220328 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -1317,7 +1317,7 @@ impl CommandLineStep for OmpOffload { let offload_clang_dir = if !builder.config.llvm_clang { // We must have an external clang to use. - builder.build.config.offload_clang_dir.clone() + builder.sess.config.offload_clang_dir.clone() } else { // No need to specify it, since we use the in-tree clang None diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs index 2ea091532ae12..cc81d9243fe26 100644 --- a/src/bootstrap/src/core/build_steps/perf.rs +++ b/src/bootstrap/src/core/build_steps/perf.rs @@ -140,7 +140,7 @@ pub fn perf(builder: &Builder<'_>, args: &PerfArgs) { target: builder.config.host_target, }); - let rustc_perf_dir = builder.build.tempdir().join("rustc-perf"); + let rustc_perf_dir = builder.sess.tempdir().join("rustc-perf"); let results_dir = rustc_perf_dir.join("results"); builder.create_dir(&results_dir); @@ -158,7 +158,7 @@ pub fn perf(builder: &Builder<'_>, args: &PerfArgs) { | PerfCommand::Cachegrind { .. } => true, PerfCommand::Benchmark { .. } | PerfCommand::Compare { .. } => false, }; - if is_profiling && builder.build.config.rust_debuginfo_level_rustc == DebuginfoLevel::None { + if is_profiling && builder.sess.config.rust_debuginfo_level_rustc == DebuginfoLevel::None { builder.info(r#"WARNING: You are compiling rustc without debuginfo, this will make profiling less useful. Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); } diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index 243b09acaa308..6051f5a27d71f 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -148,7 +148,7 @@ impl CommandLineStep for Miri { } fn run(self, builder: &Builder<'_>) { - let host = builder.build.host_target; + let host = builder.sess.host_target; let compilers = self.compilers; let target = self.target; @@ -257,7 +257,7 @@ impl CommandLineStep for GenerateCopyright { let paths_to_vendor = default_paths_to_vendor(builder); for (_, submodules) in &paths_to_vendor { for submodule in submodules { - builder.build.require_submodule(submodule, None); + builder.sess.require_submodule(submodule, None); } } let cargo_manifests = paths_to_vendor @@ -491,7 +491,7 @@ impl CommandLineStep for Rustfmt { } fn run(self, builder: &Builder<'_>) { - let host = builder.build.host_target; + let host = builder.sess.host_target; // `x run` uses stage 0 by default but rustfmt does not work well with stage 0. // Change the stage to 1 if it's not set explicitly. diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 27a400406e144..efe29ee78d741 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -173,7 +173,7 @@ impl CommandLineStep for Profile { } fn run(self, builder: &Builder<'_>) { - setup(&builder.build.config, self); + setup(&builder.sess.config, self); } } diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 5c16416266139..cb3dbe34c24bf 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -719,7 +719,7 @@ impl CommandLineStep for Miri { /// Runs `cargo test` for miri. fn run(self, builder: &Builder<'_>) { - let host = builder.build.host_target; + let host = builder.sess.host_target; let target = self.target; let stage = builder.top_stage; if stage == 0 { @@ -813,7 +813,7 @@ impl CommandLineStep for CargoMiri { /// Tests `cargo miri test`. fn run(self, builder: &Builder<'_>) { - let host = builder.build.host_target; + let host = builder.sess.host_target; let target = self.target; let stage = builder.top_stage; if stage == 0 { @@ -897,7 +897,7 @@ impl CommandLineStep for Priroda { /// Runs `cargo test` for priroda, reusing the Miri sysroot and binary. fn run(self, builder: &Builder<'_>) { - let host = builder.build.host_target; + let host = builder.sess.host_target; let target = self.target; let stage = builder.top_stage; @@ -1509,7 +1509,7 @@ fn get_browser_ui_test_version_inner( let mut command = command(yarn); command .arg("--cwd") - .arg(&builder.build.out) + .arg(&builder.sess.out) .arg("list") .arg("--parseable") .arg("--long") @@ -2285,7 +2285,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // At stage 0 (stage - 1) we are using the stage0 compiler. Using `self.target` can lead // finding an incorrect compiler path on cross-targets, as the stage 0 is always equal to // `build.build` in the configuration. - let build = builder.build.host_target; + let build = builder.sess.host_target; test_compiler = builder.compiler(test_compiler.stage - 1, build); let test_stage = test_compiler.stage + 1; (test_stage, format!("stage{test_stage}-{build}")) @@ -2522,11 +2522,11 @@ Please disable assertions with `rust.debug-assertions = false`. cmd.arg("--bypass-ignore-backends"); } - if builder.build.config.llvm_enzyme { + if builder.sess.config.llvm_enzyme { cmd.arg("--has-enzyme"); } - if builder.build.config.llvm_offload { + if builder.sess.config.llvm_offload { cmd.arg("--has-offload"); } @@ -4016,7 +4016,7 @@ impl CommandLineStep for BootstrapPy { // Forward command-line args after `--` to unittest, for filtering etc. .args(builder.config.test_args()) .env("BUILD_DIR", &builder.out) - .env("BUILD_PLATFORM", builder.build.host_target.triple) + .env("BUILD_PLATFORM", builder.sess.host_target.triple) .env("BOOTSTRAP_TEST_RUSTC_BIN", &builder.initial_rustc) .env("BOOTSTRAP_TEST_CARGO_BIN", &builder.initial_cargo) .current_dir(builder.src.join("src/bootstrap/")); @@ -4049,7 +4049,7 @@ impl CommandLineStep for Bootstrap { let record_failed_tests = builder.ensure(SetupFailedTestsFile); // Some tests require cargo submodule to be present. - builder.build.require_submodule("src/tools/cargo", None); + builder.sess.require_submodule("src/tools/cargo", None); let mut cargo = tool::prepare_tool_cargo( builder, diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index e70352158feee..922784fb7e13a 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -212,7 +212,7 @@ pub fn prepare_tool_cargo( cargo.arg("--manifest-path").arg(dir.join("Cargo.toml")); let mut features = extra_features.to_vec(); - if builder.build.config.cargo_native_static { + if builder.sess.config.cargo_native_static { if path.ends_with("cargo") || path.ends_with("clippy") || path.ends_with("miri") @@ -865,7 +865,7 @@ impl CommandLineStep for Cargo { } fn run(self, builder: &Builder<'_>) -> ToolBuildResult { - builder.build.require_submodule("src/tools/cargo", None); + builder.sess.require_submodule("src/tools/cargo", None); builder.std(self.build_compiler, builder.host_target); builder.std(self.build_compiler, self.target); @@ -1523,7 +1523,7 @@ fn extended_rustc_tool_is_default_step( && builder.config.tools.as_ref().map_or( // By default, on nightly/dev enable all tools, else only // build stable tools. - stable || builder.build.unstable_features(), + stable || builder.sess.unstable_features(), // If `tools` is set, search list for this tool. |tools| { tools.iter().any(|tool| match tool.as_ref() { diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs index 1bf9331500f8b..c6dd815d532ac 100644 --- a/src/bootstrap/src/core/build_steps/vendor.rs +++ b/src/bootstrap/src/core/build_steps/vendor.rs @@ -101,7 +101,7 @@ impl CommandLineStep for Vendor { // These submodules must be present for `x vendor` to work. for (_, submodules) in &to_vendor { for submodule in submodules { - builder.build.require_submodule(submodule, None); + builder.sess.require_submodule(submodule, None); } } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 81e95c5a6f4ac..83598a52ed62b 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -646,7 +646,7 @@ impl Builder<'_> { // from out of tree it shouldn't matter, since x.py is only used for // building in-tree. let color_logs = ["RUSTDOC_LOG_COLOR", "RUSTC_LOG_COLOR", "RUST_LOG_COLOR"]; - match self.build.config.color { + match self.sess.config.color { Color::Always => { cargo.arg("--color=always"); for log in &color_logs { @@ -1172,14 +1172,14 @@ impl Builder<'_> { match mode { Mode::Rustc | Mode::Codegen => { if let Some(ref map_to) = - self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) + self.sess.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) { // Tell the compiler which prefix was used for remapping the standard library cargo.env("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR", map_to); } if let Some(ref map_to) = - self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::Compiler) + self.sess.debuginfo_map_to(GitRepo::Rustc, RemapScheme::Compiler) { // Tell the compiler which prefix was used for remapping the compiler it-self cargo.env("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR", map_to); @@ -1190,14 +1190,14 @@ impl Builder<'_> { format!("compiler/={map_to}/compiler"), // rustc creates absolute paths (in part bc of the `rust-src` unremap // and for working directory) so let's remap the build directory as well. - format!("{}={map_to}", self.build.src.display()), + format!("{}={map_to}", self.sess.src.display()), // remap OUT_DIR so they don't leak into artifacts. - format!("{}={map_to}/out", self.build.out.display()), + format!("{}={map_to}/out", self.sess.out.display()), // on windows, rustc may use forward slashes internally #[cfg(windows)] format!( "{}={map_to}\\out", - self.build.out.display().to_string().replace('/', "\\") + self.sess.out.display().to_string().replace('/', "\\") ), ] .join("\t"); @@ -1210,7 +1210,7 @@ impl Builder<'_> { | Mode::ToolStd | Mode::ToolTarget => { if let Some(ref map_to) = - self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) + self.sess.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) { // When building the standard library sources, we want to apply the std remap scheme. let map = [ @@ -1218,14 +1218,14 @@ impl Builder<'_> { format!("library/={map_to}/library"), // rustc creates absolute paths (in part bc of the `rust-src` unremap // and for working directory) so let's remap the build directory as well. - format!("{}={map_to}", self.build.src.display()), + format!("{}={map_to}", self.sess.src.display()), // remap OUT_DIR so they don't leak into artifacts. - format!("{}={map_to}/out", self.build.out.display()), + format!("{}={map_to}/out", self.sess.out.display()), // on windows, rustc may use forward slashes internally #[cfg(windows)] format!( "{}={map_to}\\out", - self.build.out.display().to_string().replace('/', "\\") + self.sess.out.display().to_string().replace('/', "\\") ), ] .join("\t"); @@ -1236,7 +1236,7 @@ impl Builder<'_> { if self.config.rust_remap_debuginfo { let mut env_var = OsString::new(); - if let Some(vendor) = self.build.vendored_crates_path() { + if let Some(vendor) = self.sess.vendored_crates_path() { env_var.push(vendor); env_var.push("=/rust/deps"); } else { @@ -1261,8 +1261,8 @@ impl Builder<'_> { prepare_shims_dump_dir(self); cargo - .env("DUMP_BOOTSTRAP_SHIMS", self.build.out.join("bootstrap-shims-dump")) - .env("BUILD_OUT", &self.build.out) + .env("DUMP_BOOTSTRAP_SHIMS", self.sess.out.join("bootstrap-shims-dump")) + .env("BUILD_OUT", &self.sess.out) .env("CARGO_HOME", t!(home::cargo_home())); }; diff --git a/src/bootstrap/src/core/builder/cli_paths/tests.rs b/src/bootstrap/src/core/builder/cli_paths/tests.rs index 3a92bf37bdf0a..a6754e063caf4 100644 --- a/src/bootstrap/src/core/builder/cli_paths/tests.rs +++ b/src/bootstrap/src/core/builder/cli_paths/tests.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::core::builder::{Builder, CommandLineStepDescription}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::tests::TestCtx; fn render_steps_for_cli_args(args_str: &str) -> String { @@ -25,11 +25,11 @@ fn render_steps_for_cli_args(args_str: &str) -> String { .hosts(hosts) .targets(targets) .create_config(); - let mut build = Build::new(config); + let mut sess = Session::new(config); // Some rustdoc test steps are only run by default if nodejs is // configured/discovered, causing inconsistency. - build.config.nodejs = Some(PathBuf::from("node")); - let mut builder = Builder::new(&build); + sess.config.nodejs = Some(PathBuf::from("node")); + let mut builder = Builder::new(&sess); // Tell the builder to log steps that it would run, instead of running them. let buf = Arc::new(Mutex::new(String::new())); diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 98fceeae9df5c..7f3a94b157efb 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -25,7 +25,7 @@ use crate::core::compiler::Compiler; use crate::core::config::flags::Subcommand; use crate::core::config::{DryRun, TargetSelection}; use crate::core::metadata::Crate; -use crate::core::session::Build; +use crate::core::session::Session; use crate::trace; use crate::utils::build_stamp::BuildStamp; use crate::utils::cache::Cache; @@ -43,7 +43,7 @@ mod tests; /// into account build configuration from e.g. bootstrap.toml. pub(crate) struct Builder<'a> { /// Build configuration from e.g. bootstrap.toml. - pub build: &'a Build, + pub sess: &'a Session, /// The stage to use. Either implicitly determined based on subcommand, or /// explicitly specified with `--stage N`. Normally this is the stage we @@ -69,7 +69,7 @@ pub(crate) struct Builder<'a> { /// "bar"]`. pub paths: Vec, - /// Cached list of submodules from self.build.src. + /// Cached list of submodules from self.sess.src. submodule_paths_cache: OnceLock>, /// When enabled by tests, this causes the top-level steps that _would_ be @@ -81,10 +81,10 @@ pub(crate) struct Builder<'a> { } impl Deref for Builder<'_> { - type Target = Build; + type Target = Session; fn deref(&self) -> &Self::Target { - self.build + self.sess } } @@ -278,7 +278,7 @@ pub struct RunConfig<'a> { impl RunConfig<'_> { pub fn build_triple(&self) -> TargetSelection { - self.builder.build.host_target + self.builder.sess.host_target } /// Return a list of crate names selected by `run.paths`. @@ -1024,19 +1024,19 @@ impl<'a> Builder<'a> { } Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std), Kind::Vendor => describe!(vendor::Vendor), - // special-cased in Build::build() + // special-cased in Session::build() Kind::Format | Kind::Perf => vec![], Kind::MiriTest | Kind::MiriSetup => unreachable!(), } } - pub fn get_help(build: &Build, kind: Kind) -> Option { + pub fn get_help(sess: &Session, kind: Kind) -> Option { let step_descriptions = Builder::get_step_descriptions(kind); if step_descriptions.is_empty() { return None; } - let builder = Self::new_internal(build, kind, vec![]); + let builder = Self::new_internal(sess, kind, vec![]); let builder = &builder; let mut should_run = ShouldRun::new(builder); @@ -1062,10 +1062,10 @@ impl<'a> Builder<'a> { Some(help) } - fn new_internal(build: &Build, kind: Kind, paths: Vec) -> Builder<'_> { + fn new_internal(sess: &Session, kind: Kind, paths: Vec) -> Builder<'_> { Builder { - build, - top_stage: build.config.stage, + sess, + top_stage: sess.config.stage, kind, cache: Cache::new(), stack: RefCell::new(Vec::new()), @@ -1076,9 +1076,9 @@ impl<'a> Builder<'a> { } } - pub fn new(build: &Build) -> Builder<'_> { - let paths = &build.config.paths; - let (kind, paths) = match build.config.cmd { + pub fn new(sess: &Session) -> Builder<'_> { + let paths = &sess.config.paths; + let (kind, paths) = match sess.config.cmd { Subcommand::Build { .. } => (Kind::Build, &paths[..]), Subcommand::Check { .. } => (Kind::Check, &paths[..]), Subcommand::Clippy { .. } => (Kind::Clippy, &paths[..]), @@ -1101,7 +1101,7 @@ impl<'a> Builder<'a> { }; StepStack::with_current(|stack| stack.clear()); - Self::new_internal(build, kind, paths.to_owned()) + Self::new_internal(sess, kind, paths.to_owned()) } pub fn execute_cli(&self) { @@ -1231,10 +1231,10 @@ impl<'a> Builder<'a> { host: TargetSelection, target: TargetSelection, ) -> Compiler { - let mut resolved_compiler = if self.build.force_use_stage2(stage) { + let mut resolved_compiler = if self.sess.force_use_stage2(stage) { trace!(target: "COMPILER_FOR", ?stage, "force_use_stage2"); self.compiler(2, self.config.host_target) - } else if self.build.force_use_stage1(stage, target) { + } else if self.sess.force_use_stage1(stage, target) { trace!(target: "COMPILER_FOR", ?stage, "force_use_stage1"); self.compiler(1, self.config.host_target) } else { @@ -1368,7 +1368,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler pub fn sysroot_libdir_relative(&self, compiler: Compiler) -> &Path { match self.config.libdir_relative() { Some(relative_libdir) if compiler.stage >= 1 => relative_libdir, - _ if compiler.stage == 0 => &self.build.initial_relative_libdir, + _ if compiler.stage == 0 => &self.sess.initial_relative_libdir, _ => Path::new("lib"), } } @@ -1436,8 +1436,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler pub fn cargo_miri_cmd(&self, run_compiler: Compiler) -> BootstrapCommand { assert!(run_compiler.stage > 0, "miri can not be invoked at stage 0"); - let compilers = - RustcPrivateCompilers::new(self, run_compiler.stage, self.build.host_target); + let compilers = RustcPrivateCompilers::new(self, run_compiler.stage, self.sess.host_target); assert_eq!(run_compiler, compilers.target_compiler()); // Prepare the tools @@ -1467,7 +1466,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler .config .initial_cargo_clippy .clone() - .unwrap_or_else(|| self.build.config.download_clippy()); + .unwrap_or_else(|| self.sess.config.download_clippy()); let mut cmd = command(cargo_clippy); cmd.env("CARGO", &self.initial_cargo); @@ -1583,7 +1582,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler #[cfg(feature = "tracing")] { if let Some(parent) = stack.last() { - let mut graph = self.build.step_graph.borrow_mut(); + let mut graph = self.sess.step_graph.borrow_mut(); graph.register_cached_step(&step, parent, self.config.dry_run()); } } @@ -1593,7 +1592,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler #[cfg(feature = "tracing")] { let parent = stack.last(); - let mut graph = self.build.step_graph.borrow_mut(); + let mut graph = self.sess.step_graph.borrow_mut(); graph.register_step_execution(&step, parent, self.config.dry_run()); } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 5ef46a25a1e4f..fed59c3b14f2c 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -23,8 +23,8 @@ fn configure_with_args(cmd: &[&str], host: &[&str], target: &[&str]) -> Config { } fn run_build(paths: &[PathBuf], config: Config) -> Cache { - let build = Build::new(config); - let builder = Builder::new(&build); + let sess = Session::new(config); + let builder = Builder::new(&sess); builder.run_step_descriptions(&Builder::get_step_descriptions(builder.kind), paths); builder.cache } @@ -114,13 +114,13 @@ fn parse_config_download_rustc_at(path: &Path, download_rustc: &str, ci: bool) - mod sysroot_target_dirs { use super::{ - Build, Builder, Compiler, TEST_TRIPLE_1, TEST_TRIPLE_2, TargetSelection, configure, + Builder, Compiler, Session, TEST_TRIPLE_1, TEST_TRIPLE_2, TargetSelection, configure, }; #[test] fn test_sysroot_target_libdir() { - let build = Build::new(configure("build", &[TEST_TRIPLE_1], &[TEST_TRIPLE_1])); - let builder = Builder::new(&build); + let sess = Session::new(configure("build", &[TEST_TRIPLE_1], &[TEST_TRIPLE_1])); + let builder = Builder::new(&sess); let target_triple_1 = TargetSelection::from_user(TEST_TRIPLE_1); let compiler = Compiler::new(1, target_triple_1); let target_triple_2 = TargetSelection::from_user(TEST_TRIPLE_2); @@ -139,8 +139,8 @@ mod sysroot_target_dirs { #[test] fn test_sysroot_target_bindir() { - let build = Build::new(configure("build", &[TEST_TRIPLE_1], &[TEST_TRIPLE_1])); - let builder = Builder::new(&build); + let sess = Session::new(configure("build", &[TEST_TRIPLE_1], &[TEST_TRIPLE_1])); + let builder = Builder::new(&sess); let target_triple_1 = TargetSelection::from_user(TEST_TRIPLE_1); let compiler = Compiler::new(1, target_triple_1); let target_triple_2 = TargetSelection::from_user(TEST_TRIPLE_2); @@ -242,8 +242,8 @@ fn test_prebuilt_llvm_config_path_resolution() { "#, ); - let build = Build::new(config); - let builder = Builder::new(&build); + let sess = Session::new(config); + let builder = Builder::new(&sess); let expected = PathBuf::from("/some/path/to/llvm-config"); @@ -270,8 +270,8 @@ fn test_prebuilt_llvm_config_path_resolution() { "#, ); - let build = Build::new(config.clone()); - let builder = Builder::new(&build); + let sess = Session::new(config.clone()); + let builder = Builder::new(&sess); let actual = get_llvm_build_status(&builder, builder.config.host_target) .llvm_output() @@ -293,8 +293,8 @@ fn test_prebuilt_llvm_config_path_resolution() { // CI-LLVM isn't always available; check if it's enabled before testing. if config.llvm_ci_mode.download_from_ci() { - let build = Build::new(config.clone()); - let builder = Builder::new(&build); + let sess = Session::new(config.clone()); + let builder = Builder::new(&sess); let actual = get_llvm_build_status(&builder, builder.config.host_target) .llvm_output() @@ -317,8 +317,8 @@ fn test_is_builder_target() { for (target1, target2) in [(target1, target2), (target2, target1)] { let mut config = configure("build", &[], &[]); config.host_target = target1; - let build = Build::new(config); - let builder = Builder::new(&build); + let sess = Session::new(config); + let builder = Builder::new(&sess); assert!(builder.config.is_host_target(target1)); assert!(!builder.config.is_host_target(target2)); @@ -3126,8 +3126,8 @@ impl ConfigBuilder { fn run(self) -> Cache { let config = self.create_config(); - let build = Build::new(config); - let builder = Builder::new(&build); + let sess = Session::new(config); + let builder = Builder::new(&sess); builder .run_step_descriptions(&Builder::get_step_descriptions(builder.kind), &builder.paths); builder.cache diff --git a/src/bootstrap/src/core/compiler.rs b/src/bootstrap/src/core/compiler.rs index 5602e8ffd1efd..f0db8b3bd8ce4 100644 --- a/src/bootstrap/src/core/compiler.rs +++ b/src/bootstrap/src/core/compiler.rs @@ -1,7 +1,7 @@ use std::hash::{Hash, Hasher}; use crate::core::config::TargetSelection; -use crate::core::session::Build; +use crate::core::session::Session; /// A structure representing a Rust compiler. /// @@ -39,9 +39,9 @@ impl Compiler { self.forced_compiler = forced_compiler; } - /// Returns `true` if this is a snapshot compiler for `build`'s configuration - pub(crate) fn is_snapshot(&self, build: &Build) -> bool { - self.stage == 0 && self.host == build.host_target + /// Returns `true` if this is a snapshot compiler for the session's configuration + pub(crate) fn is_snapshot(&self, sess: &Session) -> bool { + self.stage == 0 && self.host == sess.host_target } /// Indicates whether the compiler was forced to use a specific stage. diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index d96300e0789aa..6df39a0927738 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -94,7 +94,7 @@ pub(crate) struct Config { pub bypass_bootstrap_lock: bool, pub ccache: Option, pub sde: Option, - /// Call Build::ninja() instead of this. + /// Call `Session::ninja` instead of this. pub ninja_in_file: bool, pub submodules: Option, pub compiler_docs: bool, @@ -1853,7 +1853,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to /// /// This *does not* update the submodule if `bootstrap.toml` explicitly says /// not to, or if we're not in a git repository (like a plain source - /// tarball). Typically [`crate::core::session::Build::require_submodule`] should be + /// tarball). Typically [`crate::core::session::Session::require_submodule`] should be /// used instead to provide a nice error to the user if the submodule is /// missing. #[cfg_attr( diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index da479251c68ab..109eb916d3fbd 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -17,7 +17,7 @@ use crate::core::build_steps::test::TestTarget; use crate::core::builder::{Builder, Kind}; use crate::core::config::Config; use crate::core::config::target_selection::{TargetSelectionList, target_selection_list}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::helpers; #[derive(Copy, Clone, Default, Debug, ValueEnum)] @@ -223,8 +223,8 @@ impl Flags { println!("NOTE: updating submodules before printing available paths"); let flags = Self::parse(&[String::from("build")]); let config = Config::parse(flags); - let build = Build::new(config); - let paths = Builder::get_help(&build, subcommand); + let sess = Session::new(config); + let paths = Builder::get_help(&sess, subcommand); if let Some(s) = paths { println!("{s}"); } else { diff --git a/src/bootstrap/src/core/metadata.rs b/src/bootstrap/src/core/metadata.rs index 5e88277008971..8e66598fda993 100644 --- a/src/bootstrap/src/core/metadata.rs +++ b/src/bootstrap/src/core/metadata.rs @@ -11,7 +11,7 @@ use std::path::PathBuf; use serde_derive::Deserialize; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::exec::command; use crate::utils::helpers::t; @@ -24,8 +24,8 @@ pub(crate) struct Crate { } impl Crate { - pub(crate) fn local_path(&self, build: &Build) -> PathBuf { - self.path.strip_prefix(&build.config.src).unwrap().into() + pub(crate) fn local_path(&self, sess: &Session) -> PathBuf { + self.path.strip_prefix(&sess.config.src).unwrap().into() } } @@ -55,10 +55,10 @@ struct Dependency { source: Option, } -/// Collects and stores package metadata of each workspace members into `build`, +/// Collects and stores package metadata of each workspace members into `sess`, /// by executing `cargo metadata` commands. -pub fn build(build: &mut Build) { - for package in workspace_members(build) { +pub(crate) fn build(sess: &mut Session) { + for package in workspace_members(sess) { if package.source.is_none() { let name = package.name; let mut path = PathBuf::from(package.manifest_path); @@ -75,9 +75,9 @@ pub fn build(build: &mut Build) { path, features: package.features.keys().cloned().collect(), }; - let relative_path = krate.local_path(build); - build.crates.insert(name.clone(), krate); - let existing_path = build.crate_paths.insert(relative_path, name); + let relative_path = krate.local_path(sess); + sess.crates.insert(name.clone(), krate); + let existing_path = sess.crate_paths.insert(relative_path, name); assert!( existing_path.is_none(), "multiple crates with the same path: {}", @@ -91,9 +91,9 @@ pub fn build(build: &mut Build) { /// /// This is used to resolve specific crate paths in `fn should_run` to compile /// particular crate (e.g., `x build sysroot` to build library/sysroot). -fn workspace_members(build: &Build) -> Vec { +fn workspace_members(sess: &Session) -> Vec { let collect_metadata = |manifest_path| { - let mut cargo = command(&build.initial_cargo); + let mut cargo = command(&sess.initial_cargo); cargo // Will read the libstd Cargo.toml // which uses the unstable `public-dependency` feature. @@ -103,8 +103,8 @@ fn workspace_members(build: &Build) -> Vec { .arg("1") .arg("--no-deps") .arg("--manifest-path") - .arg(build.src.join(manifest_path)); - let metadata_output = cargo.run_in_dry_run().run_capture_stdout(build).stdout(); + .arg(sess.src.join(manifest_path)); + let metadata_output = cargo.run_in_dry_run().run_capture_stdout(sess).stdout(); let Output { packages, .. } = t!(serde_json::from_str(&metadata_output)); packages }; diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 148f2ac1212c0..27638810fb7bb 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -18,7 +18,7 @@ use crate::core::build_steps::tool; use crate::core::builder::Builder; use crate::core::config::flags::Subcommand; use crate::core::config::{CompilerBuiltins, DebuggerPath, Target}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::exec::command; use crate::utils::helpers::{self, t}; @@ -78,15 +78,15 @@ impl Finder { } } -pub fn check(build: &mut Build) { +pub(crate) fn check(sess: &mut Session) { let mut skip_target_sanity = env::var_os("BOOTSTRAP_SKIP_TARGET_SANITY").is_some_and(|s| s == "1" || s == "true"); - skip_target_sanity |= matches!(build.config.cmd, Subcommand::Check { .. }); + skip_target_sanity |= matches!(sess.config.cmd, Subcommand::Check { .. }); // Skip target sanity checks when we are doing anything with mir-opt tests or Miri let skipped_paths = [OsStr::new("mir-opt"), OsStr::new("miri")]; - skip_target_sanity |= build.config.paths.iter().any(|path| { + skip_target_sanity |= sess.config.paths.iter().any(|path| { path.components().any(|component| skipped_paths.contains(&component.as_os_str())) }); @@ -102,18 +102,18 @@ pub fn check(build: &mut Build) { let mut cmd_finder = Finder::new(); // If we've got a git directory we're gonna need git to update // submodules and learn about various other aspects. - if build.rust_info().is_managed_git_subrepository() { + if sess.rust_info().is_managed_git_subrepository() { cmd_finder.must_have("git"); } // Ensure that a compatible version of libstdc++ is available on the system when using `llvm.download-ci-llvm`. if cfg!(not(test)) - && !build.config.dry_run() - && !build.host_target.is_msvc() - && build.config.llvm_ci_mode.download_from_ci() + && !sess.config.dry_run() + && !sess.host_target.is_msvc() + && sess.config.llvm_ci_mode.download_from_ci() { - let builder = Builder::new(build); - let libcxx_version = builder.ensure(tool::LibcxxVersionTool { target: build.host_target }); + let builder = Builder::new(sess); + let libcxx_version = builder.ensure(tool::LibcxxVersionTool { target: sess.host_target }); match libcxx_version { tool::LibcxxVersion::Gnu(version) => { @@ -138,11 +138,11 @@ pub fn check(build: &mut Build) { } // We need cmake, but only if we're actually building LLVM or sanitizers. - let building_llvm = !build.config.llvm_ci_mode.download_from_ci() - && !build.config.local_rebuild - && build.hosts.iter().any(|host| { - build.config.llvm_enabled(*host) - && build + let building_llvm = !sess.config.llvm_ci_mode.download_from_ci() + && !sess.config.local_rebuild + && sess.hosts.iter().any(|host| { + sess.config.llvm_enabled(*host) + && sess .config .target_config .get(host) @@ -150,7 +150,7 @@ pub fn check(build: &mut Build) { .unwrap_or(true) }); - let need_cmake = building_llvm || build.config.any_sanitizers_to_build(); + let need_cmake = building_llvm || sess.config.any_sanitizers_to_build(); if need_cmake && cmd_finder.maybe_have("cmake").is_none() { eprintln!( " @@ -164,7 +164,7 @@ than building it. helpers::exit_process(1); } - build.config.python = build + sess.config.python = sess .config .python .take() @@ -174,7 +174,7 @@ than building it. .or_else(|| cmd_finder.maybe_have("python3")) .or_else(|| cmd_finder.maybe_have("python2")); - build.config.nodejs = build + sess.config.nodejs = sess .config .nodejs .take() @@ -182,29 +182,29 @@ than building it. .or_else(|| cmd_finder.maybe_have("node")) .or_else(|| cmd_finder.maybe_have("nodejs")); - build.config.yarn = build + sess.config.yarn = sess .config .yarn .take() .map(|p| cmd_finder.must_have(p)) .or_else(|| cmd_finder.maybe_have("yarn")); - build.config.gdb = build.config.gdb.take().map(|p| match p { + sess.config.gdb = sess.config.gdb.take().map(|p| match p { DebuggerPath::Discover => DebuggerPath::Discover, DebuggerPath::Path(path) => DebuggerPath::Path(cmd_finder.must_have(path)), }); - build.config.reuse = build + sess.config.reuse = sess .config .reuse .take() .map(|p| cmd_finder.must_have(p)) .or_else(|| cmd_finder.maybe_have("reuse")); - let stage0_supported_target_list: HashSet = command(&build.config.initial_rustc) + let stage0_supported_target_list: HashSet = command(&sess.config.initial_rustc) .args(["--print", "target-list"]) .run_in_dry_run() - .run_capture_stdout(&build) + .run_capture_stdout(&sess) .stdout() .lines() .map(|s| s.to_string()) @@ -214,9 +214,9 @@ than building it. // because they are not needed. // // See `cc_detect::find` for more details. - let skip_tools_checks = build.config.dry_run() + let skip_tools_checks = sess.config.dry_run() || matches!( - build.config.cmd, + sess.config.cmd, Subcommand::Clean { .. } | Subcommand::Check { .. } | Subcommand::Format { .. } @@ -225,7 +225,7 @@ than building it. // We're gonna build some custom C code here and there, host triples // also build some C++ shims for LLVM so we need a C++ compiler. - for target in &build.targets { + for target in &sess.targets { // On emscripten we don't actually need the C compiler to just // build the target artifacts, only for testing. For the sake // of easier bot configuration, just skip detection. @@ -243,12 +243,12 @@ than building it. } // skip check for cross-targets - if skip_target_sanity && target != &build.host_target { + if skip_target_sanity && target != &sess.host_target { continue; } // Ignore fake targets that are only used for unit tests in bootstrap. - if cfg!(not(test)) && !skip_target_sanity && !build.local_rebuild { + if cfg!(not(test)) && !skip_target_sanity && !sess.local_rebuild { let mut has_target = false; let target_str = target.to_string(); @@ -301,33 +301,32 @@ than building it. } if !skip_tools_checks { - cmd_finder.must_have(build.cc(*target)); - if let Some(ar) = build.ar(*target) { + cmd_finder.must_have(sess.cc(*target)); + if let Some(ar) = sess.ar(*target) { cmd_finder.must_have(ar); } } } if !skip_tools_checks { - for host in &build.hosts { - cmd_finder.must_have(build.cxx(*host).unwrap()); + for host in &sess.hosts { + cmd_finder.must_have(sess.cxx(*host).unwrap()); } } - for target in &build.targets { - build - .config + for target in &sess.targets { + sess.config .target_config .entry(*target) .or_insert_with(|| Target::from_triple(&target.triple)); // compiler-rt c fallbacks for wasm cannot be built with gcc if target.contains("wasm") - && (*build.config.optimized_compiler_builtins(*target) + && (*sess.config.optimized_compiler_builtins(*target) != CompilerBuiltins::BuildRustOnly - || build.config.rust_std_features.contains("compiler-builtins-c")) + || sess.config.rust_std_features.contains("compiler-builtins-c")) { - let cc_tool = build.cc_tool(*target); + let cc_tool = sess.cc_tool(*target); if !cc_tool.is_like_clang() && !cc_tool.path().ends_with("emcc") { // emcc works as well panic!( @@ -340,19 +339,19 @@ than building it. } if (target.contains("-none-") || target.contains("nvptx")) - && build.no_std(*target) == Some(false) + && sess.no_std(*target) == Some(false) { panic!("All the *-none-* and nvptx* targets are no-std targets") } // skip check for cross-targets - if skip_target_sanity && target != &build.host_target { + if skip_target_sanity && target != &sess.host_target { continue; } // Make sure musl-root is valid. if target.contains("musl") && !target.contains("unikraft") { - match build.musl_libdir(*target) { + match sess.musl_libdir(*target) { Some(libdir) => { if fs::metadata(libdir.join("libc.a")).is_err() { panic!("couldn't find libc.a in musl libdir: {}", libdir.display()); @@ -371,7 +370,7 @@ than building it. // Cygwin. The Cygwin build does not have generators for Visual // Studio, so detect that here and error. let out = - command("cmake").arg("--help").run_in_dry_run().run_capture_stdout(&build).stdout(); + command("cmake").arg("--help").run_in_dry_run().run_capture_stdout(&sess).stdout(); if !out.contains("Visual Studio") { panic!( " @@ -395,15 +394,15 @@ $ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake // but if it's disabled then double-check it's present on the system. if target.contains("wasip") && !target.contains("wasip1") - && !build.tool_enabled("wasm-component-ld") + && !sess.tool_enabled("wasm-component-ld") { cmd_finder.must_have("wasm-component-ld"); } // aarch64-unknown-linux-pauthtest must use clang if !skip_tools_checks && target.is_pauthtest() { - let cc_tool = build.cc_tool(*target); - let linker_path = build + let cc_tool = sess.cc_tool(*target); + let linker_path = sess .linker(*target) .unwrap_or_else(|| panic!("{} requires an explicit clang linker", target.triple)); @@ -431,7 +430,7 @@ $ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake } let output = - command(cc_tool.path()).arg("-dumpversion").run_capture_stdout(&build).stdout(); + command(cc_tool.path()).arg("-dumpversion").run_capture_stdout(&sess).stdout(); let version_str = output.trim(); let mut parts = version_str.split('.').map(|s| s.parse::().unwrap_or(0)); let major = parts.next().unwrap_or(0); @@ -448,7 +447,7 @@ $ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake } } - if let Some(ref s) = build.config.ccache { + if let Some(ref s) = sess.config.ccache { cmd_finder.must_have(s); } } diff --git a/src/bootstrap/src/core/session.rs b/src/bootstrap/src/core/session.rs index 3e6668258c641..f01a4e27c4e48 100644 --- a/src/bootstrap/src/core/session.rs +++ b/src/bootstrap/src/core/session.rs @@ -39,13 +39,8 @@ pub(crate) enum GitRepo { /// This structure transitively contains all configuration for the build system. /// All filesystem-encoded configuration is in `config`, all flags are in /// `flags`, and then parsed or probed information is listed in the keys below. -/// -/// This structure is a parameter of almost all methods in the build system, -/// although most functions are implemented as free functions rather than -/// methods specifically on this structure itself (to make it easier to -/// organize). -pub(crate) struct Build { - /// User-specified configuration from `bootstrap.toml`. +pub(crate) struct Session { + /// User-specified configuration from command-line flags and `bootstrap.toml`. pub(crate) config: Config, // Version information @@ -227,8 +222,8 @@ impl FileType { } macro_rules! forward { - ( $( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => { - impl Build { + ($( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => { + impl Session { $( pub(crate) fn $fn(&self, $($param: $ty),* ) $( -> $ret)? { self.config.$fn( $($param),* ) @@ -266,12 +261,12 @@ impl From for TargetAndStage { } } -impl Build { +impl Session { /// Creates a new set of build configuration from the `flags` on the command /// line and the filesystem `config`. /// /// By default all build output will be placed in the current directory. - pub(crate) fn new(mut config: Config) -> Build { + pub(crate) fn new(mut config: Config) -> Session { let src = config.src.clone(); let out = config.out.clone(); @@ -361,7 +356,7 @@ impl Build { config.description = Some("built from a source tarball".to_owned()); } - let mut build = Build { + let mut sess = Session { initial_lld, initial_relative_libdir, initial_rustc: config.initial_rustc.clone(), @@ -410,10 +405,10 @@ impl Build { // If local-rust is the same major.minor as the current version, then force a // local-rebuild - let local_version_verbose = command(&build.initial_rustc) + let local_version_verbose = command(&sess.initial_rustc) .run_in_dry_run() .args(["--version", "--verbose"]) - .run_capture_stdout(&build) + .run_capture_stdout(&sess) .stdout(); let local_release = local_version_verbose .lines() @@ -422,26 +417,26 @@ impl Build { .unwrap() .trim(); if local_release.split('.').take(2).eq(version.split('.').take(2)) { - build.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}")); - build.local_rebuild = true; + sess.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}")); + sess.local_rebuild = true; } - build.do_if_verbose(|| println!("finding compilers")); - crate::utils::cc_detect::fill_compilers(&mut build); + sess.do_if_verbose(|| println!("finding compilers")); + crate::utils::cc_detect::fill_compilers(&mut sess); // When running `setup`, the profile is about to change, so any requirements we have now may // be different on the next invocation. Don't check for them until the next time x.py is // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing. // // Similarly, for `setup` we don't actually need submodules or cargo metadata. - if !matches!(build.config.cmd, Subcommand::Setup { .. }) { - build.do_if_verbose(|| println!("running sanity check")); - crate::core::sanity::check(&mut build); + if !matches!(sess.config.cmd, Subcommand::Setup { .. }) { + sess.do_if_verbose(|| println!("running sanity check")); + crate::core::sanity::check(&mut sess); // Make sure we update these before gathering metadata so we don't get an error about missing // Cargo.toml files. let rust_submodules = ["library/backtrace"]; for s in rust_submodules { - build.require_submodule( + sess.require_submodule( s, Some( "The submodule is required for the standard library \ @@ -450,30 +445,30 @@ impl Build { ); } // Now, update all existing submodules. - build.update_existing_submodules(); + sess.update_existing_submodules(); - build.do_if_verbose(|| println!("learning about cargo")); - crate::core::metadata::build(&mut build); + sess.do_if_verbose(|| println!("learning about cargo")); + crate::core::metadata::build(&mut sess); } // Create symbolic link to use host sysroot from a consistent path (e.g., in the rust-analyzer config file). - let build_triple = build.out.join(build.host_target); + let build_triple = sess.out.join(sess.host_target); t!(fs::create_dir_all(&build_triple)); - let host = build.out.join("host"); + let host = sess.out.join("host"); if host.is_symlink() { // Left over from a previous build; overwrite it. - // This matters if `build.build` has changed between invocations. + // This matters if `sess.host_target` has changed between invocations. #[cfg(windows)] t!(fs::remove_dir(&host)); #[cfg(not(windows))] t!(fs::remove_file(&host)); } t!( - symlink_dir(&build.config, &build_triple, &host), + symlink_dir(&sess.config, &build_triple, &host), format!("symlink_dir({} => {}) failed", host.display(), build_triple.display()) ); - build + sess } /// Updates a submodule, and exits with a failure if submodule management @@ -488,10 +483,10 @@ impl Build { feature = "tracing", instrument( level = "trace", - name = "Build::require_submodule", + name = "Session::require_submodule", skip_all, fields(submodule = submodule), - ), + ) )] pub(crate) fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) { if self.rust_info().is_from_tarball() { @@ -566,7 +561,7 @@ impl Build { } /// Executes the entire build, as configured by the flags and configuration. - #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Build::build", skip_all))] + #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Session::build", skip_all))] pub(crate) fn build(&mut self) { trace!("setting up job management"); unsafe { @@ -884,7 +879,7 @@ impl Build { /// Return a `Group` guard for a [`Step`] that: /// - Performs `action` - /// - If the action is `Kind::Test`, use [`Build::msg_test`] instead. + /// - If the action is `Kind::Test`, use [`Session::msg_test`] instead. /// - On `what` /// - Where `what` possibly corresponds to a `mode` /// - `action` is performed with/on the given compiler (`target_and_stage`). @@ -907,7 +902,7 @@ impl Build { let action = action.into(); assert!( action != Kind::Test, - "Please use `Build::msg_test` instead of `Build::msg(Kind::Test)`" + "Please use `Session::msg_test` instead of `Session::msg(Kind::Test)`" ); let actual_stage = match mode.into() { @@ -946,7 +941,7 @@ impl Build { } /// Return a `Group` guard for a [`Step`] that tests `what` with the given `stage` and `target`. - /// Use this instead of [`Build::msg`] for test steps, because for them it is not always clear + /// Use this instead of [`Session::msg`] for test steps, because for them it is not always clear /// what exactly is a build compiler. /// /// [`Step`]: crate::core::builder::Step @@ -1863,7 +1858,7 @@ to download LLVM rather than building it. } } -impl AsRef for Build { +impl AsRef for Session { fn as_ref(&self) -> &ExecutionContext { &self.config.exec_ctx } diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index e753ee71683fd..24308cdadaa4a 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -27,29 +27,29 @@ use std::path::{Path, PathBuf}; use crate::core::config::flags::Subcommand; use crate::core::config::{CompressDebuginfo, TargetSelection}; -use crate::core::session::{Build, CLang, GitRepo}; +use crate::core::session::{CLang, GitRepo, Session}; use crate::utils::exec::{BootstrapCommand, command}; /// Creates and configures a new [`cc::Build`] instance for the given target. -fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { +fn new_cc_build(sess: &Session, target: TargetSelection) -> cc::Build { let mut cfg = cc::Build::new(); cfg.cargo_metadata(false) .opt_level(2) .warnings(false) .debug(false) // We have to configure out_dir, otherwise flag_if_supported will not work - .out_dir(build.tempdir().join("cc-rs-out-dir")) + .out_dir(sess.tempdir().join("cc-rs-out-dir")) .target(&target.triple) - .host(&build.host_target.triple); + .host(&sess.host_target.triple); - match build.config.compress_debuginfo(target) { + match sess.config.compress_debuginfo(target) { CompressDebuginfo::Zlib => { cfg.flag_if_supported("-gz"); } CompressDebuginfo::Off => {} } - match build.crt_static(target) { + match sess.crt_static(target) { Some(a) => { cfg.static_crt(a); } @@ -62,31 +62,30 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { cfg } -/// Probes for C and C++ compilers and configures the corresponding entries in the [`Build`] +/// Probes for C and C++ compilers and configures the corresponding entries in the [`Session`] /// structure. /// /// This function determines which targets need a C compiler (and, if needed, a C++ compiler) /// by combining the primary build target, host targets, and any additional targets. For /// each target, it calls [`fill_target_compiler`] to configure the necessary compiler tools. -pub fn fill_compilers(build: &mut Build) { - let mut targets: HashSet<_> = match build.config.cmd { +pub(crate) fn fill_compilers(sess: &mut Session) { + let mut targets: HashSet<_> = match sess.config.cmd { // We don't need to check cross targets for these commands. Subcommand::Clean { .. } | Subcommand::Check { .. } | Subcommand::Format { .. } | Subcommand::Setup { .. } => { - build.hosts.iter().cloned().chain(iter::once(build.host_target)).collect() + sess.hosts.iter().cloned().chain(iter::once(sess.host_target)).collect() } _ => { // For all targets we're going to need a C compiler for building some shims // and such as well as for being a linker for Rust code. - build - .targets + sess.targets .iter() - .chain(&build.hosts) + .chain(&sess.hosts) .cloned() - .chain(iter::once(build.host_target)) + .chain(iter::once(sess.host_target)) .collect() } }; @@ -94,12 +93,12 @@ pub fn fill_compilers(build: &mut Build) { // When we intend to build wasm proc macros, we'll need to detect a toolchain for linking those // as well. In the future it would be good to make this a no-op given that we shouldn't need to // build any C/C++ code for wasm... - if build.config.wasm_proc_macros { + if sess.config.wasm_proc_macros { targets.insert(TargetSelection::from_user("wasm32-wasip2")); } for target in targets { - fill_target_compiler(build, target); + fill_target_compiler(sess, target); } } @@ -108,12 +107,12 @@ pub fn fill_compilers(build: &mut Build) { /// This function uses both user-specified configuration (from `bootstrap.toml`) and auto-detection /// logic to determine the correct C/C++ compilers for the target. It also determines the appropriate /// archiver (`ar`) and sets up additional compilation flags (both handled and unhandled). -pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { - let mut cfg = new_cc_build(build, target); - let config = build.config.target_config.get(&target); +fn fill_target_compiler(sess: &mut Session, target: TargetSelection) { + let mut cfg = new_cc_build(sess, target); + let config = sess.config.target_config.get(&target); if let Some(cc) = config .and_then(|c| c.cc.clone()) - .or_else(|| default_compiler(&cfg, Language::C, target, build)) + .or_else(|| default_compiler(&cfg, Language::C, target, sess)) { cfg.compiler(cc); } @@ -123,17 +122,17 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { .and_then(|c| c.ar.clone()) .or_else(|| cfg.try_get_archiver().map(|c| PathBuf::from(c.get_program())).ok()); - build.cc.insert(target, compiler.clone()); - let mut cflags = build.cc_handled_cflags(target, CLang::C); - cflags.extend(build.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::C)); + sess.cc.insert(target, compiler.clone()); + let mut cflags = sess.cc_handled_cflags(target, CLang::C); + cflags.extend(sess.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::C)); // If we use llvm-libunwind, we will need a C++ compiler as well for all targets // We'll need one anyways if the target triple is also a host triple - let mut cfg = new_cc_build(build, target); + let mut cfg = new_cc_build(sess, target); cfg.cpp(true); let cxx_configured = if let Some(cxx) = config .and_then(|c| c.cxx.clone()) - .or_else(|| default_compiler(&cfg, Language::CPlusPlus, target, build)) + .or_else(|| default_compiler(&cfg, Language::CPlusPlus, target, sess)) { cfg.compiler(cxx); true @@ -145,24 +144,24 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { // for VxWorks, record CXX compiler which will be used in lib.rs:linker() if cxx_configured || target.contains("vxworks") { let compiler = cfg.get_compiler(); - build.cxx.insert(target, compiler); + sess.cxx.insert(target, compiler); } - build.do_if_verbose(|| println!("CC_{} = {:?}", target.triple, build.cc(target))); - build.do_if_verbose(|| println!("CFLAGS_{} = {cflags:?}", target.triple)); - if let Ok(cxx) = build.cxx(target) { - let mut cxxflags = build.cc_handled_cflags(target, CLang::Cxx); - cxxflags.extend(build.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::Cxx)); - build.do_if_verbose(|| println!("CXX_{} = {cxx:?}", target.triple)); - build.do_if_verbose(|| println!("CXXFLAGS_{} = {cxxflags:?}", target.triple)); + sess.do_if_verbose(|| println!("CC_{} = {:?}", target.triple, sess.cc(target))); + sess.do_if_verbose(|| println!("CFLAGS_{} = {cflags:?}", target.triple)); + if let Ok(cxx) = sess.cxx(target) { + let mut cxxflags = sess.cc_handled_cflags(target, CLang::Cxx); + cxxflags.extend(sess.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::Cxx)); + sess.do_if_verbose(|| println!("CXX_{} = {cxx:?}", target.triple)); + sess.do_if_verbose(|| println!("CXXFLAGS_{} = {cxxflags:?}", target.triple)); } if let Some(ar) = ar { - build.do_if_verbose(|| println!("AR_{} = {ar:?}", target.triple)); - build.ar.insert(target, ar); + sess.do_if_verbose(|| println!("AR_{} = {ar:?}", target.triple)); + sess.ar.insert(target, ar); } if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) { - build.ranlib.insert(target, ranlib); + sess.ranlib.insert(target, ranlib); } } @@ -172,14 +171,14 @@ fn default_compiler( cfg: &cc::Build, compiler: Language, target: TargetSelection, - build: &Build, + sess: &Session, ) -> Option { match &*target.triple { // When compiling for android we may have the NDK configured in the // bootstrap.toml in which case we look there. Otherwise the default // compiler already takes into account the triple in question. t if t.contains("android") => { - build.config.android_ndk.as_ref().map(|ndk| ndk_compiler(compiler, &target.triple, ndk)) + sess.config.android_ndk.as_ref().map(|ndk| ndk_compiler(compiler, &target.triple, ndk)) } // The default gcc version from OpenBSD may be too old, try using egcc, @@ -192,14 +191,14 @@ fn default_compiler( } let mut cmd = BootstrapCommand::from(c.to_command()); - let output = cmd.arg("--version").run_capture_stdout(build).stdout(); + let output = cmd.arg("--version").run_capture_stdout(sess).stdout(); let i = output.find(" 4.")?; match output[i + 3..].chars().next().unwrap() { '0'..='6' => {} _ => return None, } let alternative = format!("e{gnu_compiler}"); - if command(&alternative).run_capture(build).is_success() { + if command(&alternative).run_capture(sess).is_success() { Some(PathBuf::from(alternative)) } else { None @@ -222,7 +221,7 @@ fn default_compiler( } t if t.contains("musl") && compiler == Language::C => { - if let Some(root) = build.musl_root(target) { + if let Some(root) = sess.musl_root(target) { let guess = root.join("bin/musl-gcc"); if guess.exists() { Some(guess) } else { None } } else { @@ -231,10 +230,10 @@ fn default_compiler( } t if t.contains("-wasi") => { - let root = if let Some(path) = build.wasi_sdk_path.as_ref() { + let root = if let Some(path) = sess.wasi_sdk_path.as_ref() { path } else { - if build.config.is_running_on_ci() { + if sess.config.is_running_on_ci() { panic!("ERROR: WASI_SDK_PATH must be configured for a -wasi target on CI"); } println!("WARNING: WASI_SDK_PATH not set, using default cc/cxx compiler"); diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index 716407cb0cb1c..861c9953b52d1 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use super::*; use crate::core::config::{Target, TargetSelection}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::tests::TestCtx; #[test] @@ -70,9 +70,9 @@ fn test_language_clang() { #[test] fn test_new_cc_build() { let config = TestCtx::new().config("build").create_config(); - let build = Build::new(config); + let sess = Session::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); - let cfg = new_cc_build(&build, target.clone()); + let cfg = new_cc_build(&sess, target.clone()); let compiler = cfg.get_compiler(); assert!(!compiler.path().to_str().unwrap().is_empty(), "Compiler path should not be empty"); } @@ -80,13 +80,13 @@ fn test_new_cc_build() { #[test] fn test_default_compiler_wasi() { let config = TestCtx::new().config("build").create_config(); - let mut build = Build::new(config); + let mut sess = Session::new(config); let target = TargetSelection::from_user("wasm32-wasi"); let wasi_sdk = PathBuf::from("/wasi-sdk"); - build.wasi_sdk_path = Some(wasi_sdk.clone()); + sess.wasi_sdk_path = Some(wasi_sdk.clone()); let cfg = cc::Build::new(); - if let Some(result) = default_compiler(&cfg, Language::C, target.clone(), &build) { + if let Some(result) = default_compiler(&cfg, Language::C, target.clone(), &sess) { let expected = { let compiler = format!("{}-clang", target.triple); wasi_sdk.join("bin").join(compiler) @@ -102,59 +102,59 @@ fn test_default_compiler_wasi() { #[test] fn test_default_compiler_fallback() { let config = TestCtx::new().config("build").create_config(); - let build = Build::new(config); + let sess = Session::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let cfg = cc::Build::new(); - let result = default_compiler(&cfg, Language::C, target, &build); + let result = default_compiler(&cfg, Language::C, target, &sess); assert!(result.is_none(), "default_compiler should return None for generic targets"); } #[test] fn test_find_target_with_config() { let config = TestCtx::new().config("build").create_config(); - let mut build = Build::new(config); + let mut sess = Session::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let mut target_config = Target::default(); target_config.cc = Some(PathBuf::from("dummy-cc")); target_config.cxx = Some(PathBuf::from("dummy-cxx")); target_config.ar = Some(PathBuf::from("dummy-ar")); target_config.ranlib = Some(PathBuf::from("dummy-ranlib")); - build.config.target_config.insert(target.clone(), target_config); - fill_target_compiler(&mut build, target.clone()); - let cc_tool = build.cc.get(&target).unwrap(); + sess.config.target_config.insert(target.clone(), target_config); + fill_target_compiler(&mut sess, target.clone()); + let cc_tool = sess.cc.get(&target).unwrap(); assert_eq!(cc_tool.path(), &PathBuf::from("dummy-cc")); - let cxx_tool = build.cxx.get(&target).unwrap(); + let cxx_tool = sess.cxx.get(&target).unwrap(); assert_eq!(cxx_tool.path(), &PathBuf::from("dummy-cxx")); - let ar = build.ar.get(&target).unwrap(); + let ar = sess.ar.get(&target).unwrap(); assert_eq!(ar, &PathBuf::from("dummy-ar")); - let ranlib = build.ranlib.get(&target).unwrap(); + let ranlib = sess.ranlib.get(&target).unwrap(); assert_eq!(ranlib, &PathBuf::from("dummy-ranlib")); } #[test] fn test_find_target_without_config() { let config = TestCtx::new().config("build").create_config(); - let mut build = Build::new(config); + let mut sess = Session::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); - build.config.target_config.clear(); - fill_target_compiler(&mut build, target.clone()); - assert!(build.cc.contains_key(&target)); + sess.config.target_config.clear(); + fill_target_compiler(&mut sess, target.clone()); + assert!(sess.cc.contains_key(&target)); if !target.triple.contains("vxworks") { - assert!(build.cxx.contains_key(&target)); + assert!(sess.cxx.contains_key(&target)); } - assert!(build.ar.contains_key(&target)); + assert!(sess.ar.contains_key(&target)); } #[test] fn test_find() { let config = TestCtx::new().config("build").create_config(); - let mut build = Build::new(config); + let mut sess = Session::new(config); let target1 = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let target2 = TargetSelection::from_user("x86_64-unknown-openbsd"); - build.targets.push(target1.clone()); - build.hosts.push(target2.clone()); - fill_compilers(&mut build); - for t in build.hosts.iter().chain(build.targets.iter()).chain(iter::once(&build.host_target)) { - assert!(build.cc.contains_key(t), "CC not set for target {}", t.triple); + sess.targets.push(target1.clone()); + sess.hosts.push(target2.clone()); + fill_compilers(&mut sess); + for t in sess.hosts.iter().chain(sess.targets.iter()).chain(iter::once(&sess.host_target)) { + assert!(sess.cc.contains_key(t), "CC not set for target {}", t.triple); } } diff --git a/src/bootstrap/src/utils/channel.rs b/src/bootstrap/src/utils/channel.rs index ebb40edf9b262..0663bd0bf5215 100644 --- a/src/bootstrap/src/utils/channel.rs +++ b/src/bootstrap/src/utils/channel.rs @@ -10,7 +10,7 @@ use std::path::Path; use super::exec::ExecutionContext; use super::helpers; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::helpers::t; #[derive(Clone, Default)] @@ -111,8 +111,8 @@ impl GitInfo { self.info().map(|s| &s.commit_date[..]) } - pub fn version(&self, build: &Build, num: &str) -> String { - let mut version = build.release(num); + pub fn version(&self, sess: &Session, num: &str) -> String { + let mut version = sess.release(num); if let Some(inner) = self.info() { version.push_str(" ("); version.push_str(&inner.short_sha); diff --git a/src/bootstrap/src/utils/job.rs b/src/bootstrap/src/utils/job.rs index 942ac6c80e4ee..45cff7c716ba9 100644 --- a/src/bootstrap/src/utils/job.rs +++ b/src/bootstrap/src/utils/job.rs @@ -1,14 +1,13 @@ #[cfg(windows)] -pub use for_windows::*; - -use crate::core::session::Build; +pub(crate) use self::for_windows::setup; +use crate::core::session::Session; #[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))] -pub unsafe fn setup(_build: &mut Build) {} +pub(crate) unsafe fn setup(_sess: &Session) {} #[cfg(all(unix, not(target_os = "haiku")))] -pub unsafe fn setup(build: &mut Build) { - if build.config.low_priority { +pub(crate) unsafe fn setup(sess: &Session) { + if sess.config.low_priority { unsafe { libc::setpriority(libc::PRIO_PGRP as _, 0, 10); } @@ -60,7 +59,7 @@ mod for_windows { use windows::Win32::System::Threading::{BELOW_NORMAL_PRIORITY_CLASS, GetCurrentProcess}; use windows::core::PCWSTR; - pub unsafe fn setup(build: &mut super::Build) { + pub(crate) unsafe fn setup(sess: &super::Session) { // SAFETY: pretty much everything below is unsafe unsafe { // Enable the Windows Error Reporting dialog which msys disables, @@ -77,7 +76,7 @@ mod for_windows { // children will reside in the job by default. let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default(); info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - if build.config.low_priority { + if sess.config.low_priority { info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS; info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS.0; } diff --git a/src/bootstrap/src/utils/metrics.rs b/src/bootstrap/src/utils/metrics.rs index a309b1d53b8e9..79d967452d0c5 100644 --- a/src/bootstrap/src/utils/metrics.rs +++ b/src/bootstrap/src/utils/metrics.rs @@ -17,7 +17,7 @@ use build_helper::metrics::{ use sysinfo::{CpuRefreshKind, RefreshKind, System}; use crate::core::builder::{Builder, Step}; -use crate::core::session::Build; +use crate::core::session::Session; use crate::utils::helpers::t; // Update this number whenever a breaking change is made to the build metrics. @@ -156,11 +156,11 @@ impl BuildMetrics { step.cpu_usage_time_sec += cpu as f64 / 100.0 * elapsed.as_secs_f64(); } - pub(crate) fn persist(&self, build: &Build) { + pub(crate) fn persist(&self, sess: &Session) { let mut state = self.state.borrow_mut(); assert!(state.running_steps.is_empty(), "steps are still executing"); - let dest = build.out.join("metrics.json"); + let dest = sess.out.join("metrics.json"); let mut system = System::new_with_specifics( RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), @@ -222,7 +222,7 @@ impl BuildMetrics { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations, - ci_metadata: get_ci_metadata(build.config.ci_env), + ci_metadata: get_ci_metadata(sess.config.ci_env), }; t!(std::fs::create_dir_all(dest.parent().unwrap())); From 1bc25c317b87243245dea5d26000e7a163d7c2e5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 24 Jul 2026 10:27:42 +1000 Subject: [PATCH 24/26] Clarify token cursor behaviour The meaning of `TokenTreeCursor::index` is context-dependent: in the innermost (current) `TokenTreeCursor` it points to the next token tree, but in all the other (stack) `TokenTreeCursor`s it points to the current token tree. This makes the meanings of "current", "next", and "look_ahead" confusing for it and for `TokenCursor`. This commit clarifies things by adjusting the stack `TokenTreeCursor`s to also point to the next token tree, and by improving various comments. The commit also renames `TokenCursor::next` as `TokenCursor::next_and_bump` for consistency with everything else: `next` means "get the next thing" and `bump` means "advance the cursor", and this operation does both. --- compiler/rustc_ast/src/tokenstream.rs | 66 ++++++++++++++++---------- compiler/rustc_parse/src/parser/mod.rs | 21 ++++---- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 428f37b8af450..e860ef61a5332 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -249,14 +249,14 @@ impl LazyAttrTokenStreamInner { break_last_token, node_replacements, } => { - // The token produced by the final call to `{,inlined_}next` was not + // The token produced by the final call to `{,inlined_}next_and_bump` was not // actually consumed by the callback. The combination of chaining the // initial token and using `take` produces the desired result - we // produce an empty `TokenStream` if no calls were made, and omit the // final token otherwise. let mut cursor_snapshot = cursor_snapshot.clone(); let tokens = iter::once(FlatToken::Token(*start_token)) - .chain(iter::repeat_with(|| FlatToken::Token(cursor_snapshot.next()))) + .chain(iter::repeat_with(|| FlatToken::Token(cursor_snapshot.next_and_bump()))) .take(*num_calls as usize); if node_replacements.is_empty() { @@ -883,36 +883,48 @@ impl<'t> Iterator for TokenStreamIter<'t> { #[derive(Clone, Debug)] struct TokenTreeCursor { stream: TokenStream, - /// Points to the current token tree in the stream. In `TokenCursor::curr`, - /// this can be any token tree. In `TokenCursor::stack`, this is always a - /// `TokenTree::Delimited`. - index: usize, + /// Points to the next token tree (or one past the end of the stream). + next_idx: usize, } impl TokenTreeCursor { #[inline] fn new(stream: TokenStream) -> Self { - TokenTreeCursor { stream, index: 0 } + TokenTreeCursor { stream, next_idx: 0 } } + /// Gets the current token tree within this cursor. In a debug build it panics on a cursor that + /// hasn't been bumped; in a release build it will return `None`. #[inline] fn curr(&self) -> Option<&TokenTree> { - self.stream.get(self.index) + debug_assert!(self.next_idx > 0); + self.stream.get(self.next_idx - 1) } + /// Gets the next token tree without advancing. + #[inline] + fn next(&self) -> Option<&TokenTree> { + self.stream.get(self.next_idx) + } + + /// Gets the token tree `n` ahead. `look_ahead(1)` is equivalent to `next()`. `look_ahead(0)` + /// isn't allowed and will panic. + #[inline] fn look_ahead(&self, n: usize) -> Option<&TokenTree> { - self.stream.get(self.index + n) + assert_ne!(n, 0); + self.stream.get(self.next_idx + (n - 1)) } + /// Move the cursor to the next token tree. #[inline] fn bump(&mut self) { - self.index += 1; + self.next_idx += 1; } - // For skipping ahead in rare circumstances. + /// For skipping ahead in rare circumstances. #[inline] fn bump_to_end(&mut self) { - self.index = self.stream.len(); + self.next_idx = self.stream.len(); } } @@ -922,15 +934,16 @@ impl TokenTreeCursor { /// what the parser expects, for the most part. #[derive(Clone, Debug)] pub struct TokenCursor { - // Cursor for the current (innermost) token stream. The index within the + // Cursor for the current (innermost) token stream. The `next_idx` within the // cursor can point to any token tree in the stream (or one past the end). - // The delimiters for this token stream are found in `self.stack.last()`; - // if that is `None` we are in the outermost token stream which never has - // delimiters. + // The delimiters for this token stream are found in the current token tree + // in `self.stack.last()`; if that is `None` we are in the outermost token + // stream which never has delimiters. curr: TokenTreeCursor, - // Token streams surrounding the current one. The index within each cursor - // always points to a `TokenTree::Delimited`. + // Token streams surrounding the current one. The `next_idx` within each cursor + // is always greater than zero and always points one past the current + // `TokenTree::Delimited`. stack: Vec, } @@ -940,12 +953,13 @@ impl TokenCursor { TokenCursor { curr: TokenTreeCursor::new(stream), stack: vec![] } } - pub fn next(&mut self) -> (Token, Spacing) { - self.inlined_next() + /// Gets the next token and advances the cursor by one. + pub fn next_and_bump(&mut self) -> (Token, Spacing) { + self.inlined_next_and_bump() } - /// An `n` of zero is the next token tree in the current token stream; won't look outside the - /// current token stream. + /// An `n` of 1 is the next token tree in the current token stream; won't look outside the + /// current token stream. `look_ahead(0)` isn't allowed and will panic. #[inline] pub fn look_ahead(&self, n: usize) -> Option<&TokenTree> { self.curr.look_ahead(n) @@ -955,7 +969,7 @@ impl TokenCursor { /// delimited sequence. Panics if we are not within a delimited sequence. #[inline] pub fn look_ahead_past_close_delim(&self) -> Option<&TokenTree> { - self.stack.last().unwrap().look_ahead(1) + self.stack.last().unwrap().next() } /// Clones the `TokenTree::Delimited` that we are currently within. Panics if we are not within @@ -991,12 +1005,12 @@ impl TokenCursor { /// This always-inlined version should only be used on hot code paths. #[inline(always)] - pub fn inlined_next(&mut self) -> (Token, Spacing) { + pub fn inlined_next_and_bump(&mut self) -> (Token, Spacing) { loop { // FIXME: we currently don't return `Delimiter::Invisible` open/close delims. To fix // #67062 we will need to, whereupon the `delim != Delimiter::Invisible` conditions // below can be removed. - if let Some(tree) = self.curr.curr() { + if let Some(tree) = self.curr.next() { match tree { &TokenTree::Token(token, spacing) => { debug_assert!(!token.kind.is_delim()); @@ -1006,6 +1020,7 @@ impl TokenCursor { } &TokenTree::Delimited(sp, spacing, delim, ref tts) => { let trees = TokenTreeCursor::new(tts.clone()); + self.curr.bump(); // move past the `Delimited` self.stack.push(mem::replace(&mut self.curr, trees)); if !delim.skip() { return (Token::new(delim.as_open_token_kind(), sp.open), spacing.open); @@ -1019,7 +1034,6 @@ impl TokenCursor { panic!("parent should be Delimited") }; self.curr = parent; - self.curr.bump(); // move past the `Delimited` if !delim.skip() { return (Token::new(delim.as_close_token_kind(), span.close), spacing.close); } diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 7bbace5bdc6d2..80c1eeb4ef041 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -496,9 +496,7 @@ impl<'a> Parser<'a> { // Check the first token after the delimiter that closes the current // delimited sequence. (Panics if used in the outermost token stream, which - // has no delimiters.) It uses a clone of the relevant tree cursor to skip - // past the entire `TokenTree::Delimited` in a single step, avoiding the - // need for unbounded token lookahead. + // has no delimiters.) // // Primarily used when `self.token` matches `OpenInvisible(_))`, to look // ahead through the current metavar expansion. @@ -1125,7 +1123,7 @@ impl<'a> Parser<'a> { pub fn bump(&mut self) { // Note: destructuring here would give nicer code, but it was found in #96210 to be slower // than `.0`/`.1` access. - let mut next = self.token_cursor.inlined_next(); + let mut next = self.token_cursor.inlined_next_and_bump(); self.num_bump_calls += 1; // We got a token from the underlying cursor and no longer need to // worry about an unglued token. See `break_and_eat` for more details. @@ -1153,8 +1151,8 @@ impl<'a> Parser<'a> { // Typically around 98% of the `dist > 0` cases have `dist == 1`, so we // have a fast special case for that. if dist == 1 { - // `look_ahead(0)` returns the *next* token. - match self.token_cursor.look_ahead(0) { + // `look_ahead(1)` returns the next token. + match self.token_cursor.look_ahead(1) { Some(tree) => { // Indexing stayed within the current token tree. match tree { @@ -1180,13 +1178,13 @@ impl<'a> Parser<'a> { } } - // Just clone the token cursor and use `next`, skipping delimiters as + // Just clone the token cursor and use `next_and_bump`, skipping delimiters as // necessary. Slow but simple. let mut cursor = self.token_cursor.clone(); let mut i = 0; let mut token = Token::dummy(); while i < dist { - token = cursor.next().0; + token = cursor.next_and_bump().0; if let token::OpenInvisible(origin) | token::CloseInvisible(origin) = token.kind && origin.skip() { @@ -1198,14 +1196,13 @@ impl<'a> Parser<'a> { } /// Like `look_ahead`, but skips over token trees rather than tokens. Useful - /// when looking past possible metavariable pasting sites. + /// when looking past possible metavariable pasting sites. Panics if `dist` is zero. pub fn tree_look_ahead( &self, dist: usize, looker: impl FnOnce(&TokenTree) -> R, ) -> Option { - assert_ne!(dist, 0); - self.token_cursor.look_ahead(dist - 1).map(looker) + self.token_cursor.look_ahead(dist).map(looker) } /// Returns whether any of the given keywords are `dist` tokens ahead of the current one. @@ -1411,7 +1408,7 @@ impl<'a> Parser<'a> { debug_assert_eq!(self.token_cursor.depth(), target_depth); } else { loop { - // Advance one token at a time, so `TokenCursor::next()` + // Advance one token at a time, so `TokenCursor::next_and_bump()` // can capture these tokens if necessary. self.bump(); if self.token_cursor.depth() == target_depth { From 0c10bfa358038555506c6e708c80838fe39e0257 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:53:07 +0330 Subject: [PATCH 25/26] Add codegen test for Vec::clear lowering to an unconditional store --- .../issues/vec-clear-no-branch-45459.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/codegen-llvm/issues/vec-clear-no-branch-45459.rs diff --git a/tests/codegen-llvm/issues/vec-clear-no-branch-45459.rs b/tests/codegen-llvm/issues/vec-clear-no-branch-45459.rs new file mode 100644 index 0000000000000..e6ff4684a8a41 --- /dev/null +++ b/tests/codegen-llvm/issues/vec-clear-no-branch-45459.rs @@ -0,0 +1,19 @@ +// Tests that clearing a `Vec` of a type without drop glue lowers to an +// unconditional store of the new length, without a comparison and branch +// guarding it. +// See . + +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +// CHECK-LABEL: @clear_vec( +// CHECK-NOT: icmp +// CHECK-NOT: br {{.*}} +// CHECK: store i{{[0-9]+}} 0 +// CHECK-NOT: br {{.*}} +// CHECK: ret void +#[no_mangle] +pub fn clear_vec(v: &mut Vec) { + v.clear(); +} From 8737537a06b4f506e67d6163bd5f03ba229d82fb Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Mon, 24 Aug 2026 14:22:36 +0300 Subject: [PATCH 26/26] Add tests for delegations to inherent impls --- .../ui/delegation/auxiliary/inherent-impl.rs | 26 ++ tests/ui/delegation/inherent-impls-ambig.rs | 62 +++++ .../ui/delegation/inherent-impls-ambig.stderr | 99 +++++++ tests/ui/delegation/inherent-impls-enums.rs | 90 ++++++ .../ui/delegation/inherent-impls-enums.stderr | 159 +++++++++++ .../ui/delegation/inherent-impls-glob-list.rs | 23 ++ .../inherent-impls-glob-list.stderr | 21 ++ .../inherent-impls-mixed-generics.rs | 16 ++ .../inherent-impls-mixed-generics.stderr | 9 + .../inherent-impls-non-local-crate.rs | 25 ++ .../inherent-impls-non-local-crate.stderr | 39 +++ .../inherent-impls-parent-generics.rs | 80 ++++++ .../inherent-impls-parent-generics.stderr | 123 +++++++++ .../inherent-impls-receiver-mapping.rs | 71 +++++ .../inherent-impls-receiver-mapping.stderr | 256 ++++++++++++++++++ .../inherent-impls-recursive-cycle.rs | 52 ++++ .../inherent-impls-recursive-cycle.stderr | 80 ++++++ .../ui/delegation/inherent-impls-recursive.rs | 69 +++++ .../inherent-impls-recursive.stderr | 61 +++++ tests/ui/delegation/inherent-impls-rename.rs | 14 + .../delegation/inherent-impls-rename.stderr | 9 + .../delegation/inherent-impls-self-mapping.rs | 20 ++ .../inherent-impls-self-mapping.stderr | 15 + .../inherent-impls-self-replacement.rs | 56 ++++ .../inherent-impls-self-replacement.stderr | 75 +++++ tests/ui/delegation/inherent-impls-structs.rs | 86 ++++++ .../delegation/inherent-impls-structs.stderr | 159 +++++++++++ .../inherent-impls-wrong-header-args-ice.rs | 17 ++ ...nherent-impls-wrong-header-args-ice.stderr | 21 ++ 29 files changed, 1833 insertions(+) create mode 100644 tests/ui/delegation/auxiliary/inherent-impl.rs create mode 100644 tests/ui/delegation/inherent-impls-ambig.rs create mode 100644 tests/ui/delegation/inherent-impls-ambig.stderr create mode 100644 tests/ui/delegation/inherent-impls-enums.rs create mode 100644 tests/ui/delegation/inherent-impls-enums.stderr create mode 100644 tests/ui/delegation/inherent-impls-glob-list.rs create mode 100644 tests/ui/delegation/inherent-impls-glob-list.stderr create mode 100644 tests/ui/delegation/inherent-impls-mixed-generics.rs create mode 100644 tests/ui/delegation/inherent-impls-mixed-generics.stderr create mode 100644 tests/ui/delegation/inherent-impls-non-local-crate.rs create mode 100644 tests/ui/delegation/inherent-impls-non-local-crate.stderr create mode 100644 tests/ui/delegation/inherent-impls-parent-generics.rs create mode 100644 tests/ui/delegation/inherent-impls-parent-generics.stderr create mode 100644 tests/ui/delegation/inherent-impls-receiver-mapping.rs create mode 100644 tests/ui/delegation/inherent-impls-receiver-mapping.stderr create mode 100644 tests/ui/delegation/inherent-impls-recursive-cycle.rs create mode 100644 tests/ui/delegation/inherent-impls-recursive-cycle.stderr create mode 100644 tests/ui/delegation/inherent-impls-recursive.rs create mode 100644 tests/ui/delegation/inherent-impls-recursive.stderr create mode 100644 tests/ui/delegation/inherent-impls-rename.rs create mode 100644 tests/ui/delegation/inherent-impls-rename.stderr create mode 100644 tests/ui/delegation/inherent-impls-self-mapping.rs create mode 100644 tests/ui/delegation/inherent-impls-self-mapping.stderr create mode 100644 tests/ui/delegation/inherent-impls-self-replacement.rs create mode 100644 tests/ui/delegation/inherent-impls-self-replacement.stderr create mode 100644 tests/ui/delegation/inherent-impls-structs.rs create mode 100644 tests/ui/delegation/inherent-impls-structs.stderr create mode 100644 tests/ui/delegation/inherent-impls-wrong-header-args-ice.rs create mode 100644 tests/ui/delegation/inherent-impls-wrong-header-args-ice.stderr diff --git a/tests/ui/delegation/auxiliary/inherent-impl.rs b/tests/ui/delegation/auxiliary/inherent-impl.rs new file mode 100644 index 0000000000000..a5d2f15b2ffc6 --- /dev/null +++ b/tests/ui/delegation/auxiliary/inherent-impl.rs @@ -0,0 +1,26 @@ +#![feature(inherent_associated_types)] + +pub struct S; + +impl S { + pub type TYPE = (); + pub const CONST: usize = 0; + + pub fn foo() {} +} + +pub trait Trait { + fn bar() {} +} + +impl Trait for S {} + +pub struct X(T); + +impl X { + pub fn foo() {} +} + +impl X { + pub fn foo() {} +} diff --git a/tests/ui/delegation/inherent-impls-ambig.rs b/tests/ui/delegation/inherent-impls-ambig.rs new file mode 100644 index 0000000000000..1c419034d2446 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-ambig.rs @@ -0,0 +1,62 @@ +#![feature(fn_delegation)] + +mod test_1 { + struct X(T); + + impl X<()> { + fn foo() {} + fn foo_self(self) {} + } + + impl X { + fn foo() {} + fn foo_self(self) {} + } + + reuse X::foo; + //~^ ERROR: cannot find function `foo` in `X` + + reuse X::foo_self; + //~^ ERROR: cannot find function `foo_self` in `X` + + reuse X::<()>::foo as foo1; + //~^ ERROR: cannot find function `foo` in `X` + + reuse X::::foo_self as foo_self1; + //~^ ERROR: cannot find function `foo_self` in `X` +} + +mod test_2 { + struct X(T, U); + trait Marker1 {} + trait Marker2 {} + + impl X { + fn foo() {} + fn foo_self(self) {} + } + + impl X { + fn foo() {} + fn foo_self(self) {} + } + + struct M1; + impl Marker1 for M1 {} + struct M2; + impl Marker2 for M2 {} + + reuse X::foo; + //~^ ERROR: cannot find function `foo` in `X` + + reuse X::foo_self; + //~^ ERROR: cannot find function `foo_self` in `X` + + reuse X::::foo as foo1; + //~^ ERROR: cannot find function `foo` in `X` + + reuse X::::foo_self as foo_self1; + //~^ ERROR: cannot find function `foo_self` in `X` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-ambig.stderr b/tests/ui/delegation/inherent-impls-ambig.stderr new file mode 100644 index 0000000000000..0be82bcdd6f74 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-ambig.stderr @@ -0,0 +1,99 @@ +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-ambig.rs:16:14 + | +LL | reuse X::foo; + | ^^^ not found in `X` + | +note: function `test_2::foo` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:49:5 + | +LL | reuse X::foo; + | ^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo_self` in `X` + --> $DIR/inherent-impls-ambig.rs:19:14 + | +LL | reuse X::foo_self; + | ^^^^^^^^ not found in `X` + | +note: function `test_2::foo_self` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:52:5 + | +LL | reuse X::foo_self; + | ^^^^^^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-ambig.rs:22:20 + | +LL | reuse X::<()>::foo as foo1; + | ^^^ not found in `X` + | +note: function `test_2::foo` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:49:5 + | +LL | reuse X::foo; + | ^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo_self` in `X` + --> $DIR/inherent-impls-ambig.rs:25:23 + | +LL | reuse X::::foo_self as foo_self1; + | ^^^^^^^^ not found in `X` + | +note: function `test_2::foo_self` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:52:5 + | +LL | reuse X::foo_self; + | ^^^^^^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-ambig.rs:49:14 + | +LL | reuse X::foo; + | ^^^ not found in `X` + | +note: function `test_1::foo` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:16:5 + | +LL | reuse X::foo; + | ^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo_self` in `X` + --> $DIR/inherent-impls-ambig.rs:52:14 + | +LL | reuse X::foo_self; + | ^^^^^^^^ not found in `X` + | +note: function `test_1::foo_self` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:19:5 + | +LL | reuse X::foo_self; + | ^^^^^^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-ambig.rs:55:27 + | +LL | reuse X::::foo as foo1; + | ^^^ not found in `X` + | +note: function `test_1::foo` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:16:5 + | +LL | reuse X::foo; + | ^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo_self` in `X` + --> $DIR/inherent-impls-ambig.rs:58:28 + | +LL | reuse X::::foo_self as foo_self1; + | ^^^^^^^^ not found in `X` + | +note: function `test_1::foo_self` exists but is inaccessible + --> $DIR/inherent-impls-ambig.rs:19:5 + | +LL | reuse X::foo_self; + | ^^^^^^^^^^^^^^^^^^ not accessible + +error: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-enums.rs b/tests/ui/delegation/inherent-impls-enums.rs new file mode 100644 index 0000000000000..301cbe32b6f88 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-enums.rs @@ -0,0 +1,90 @@ +#![feature(fn_delegation)] + +enum S<'a: 'a, A: 'a, const C: usize> { + A(A), + B(&'a [A; C]), +} + +impl<'a, 'b, 'c, A: 'a, const C: usize> S<'a, A, C> { + fn foo_static<'d: 'd, 'e, T, const B: bool>() {} + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; +//~^ ERROR: cannot find function `foo_static` in enum `S` +reuse S::<'static, (), 1>::foo_static as foo_static_3; +//~^ ERROR: cannot find function `foo_static` in enum `S` +reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; +//~^ ERROR: cannot find function `foo_static` in enum `S` + +reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1; +//~^ ERROR: cannot find function `foo_self` in enum `S` +reuse S::<'static, (), 1>::foo_self as foo_self_3; +//~^ ERROR: cannot find function `foo_self` in enum `S` +reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; +//~^ ERROR: cannot find function `foo_self` in enum `S` + +trait Trait<'a, AA, BB> +where + Self: Sized, +{ + reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in enum `S` + + fn get_s(self) -> S<'static, (), 1> { + panic!(); + } + + reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in enum `S` +} + +struct X; + +impl<'a, A, B> Trait<'a, A, B> for X { + reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in enum `S` + + reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + //~| ERROR: delegation's target expression is specified for function with no params + reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + //~| ERROR: delegation's target expression is specified for function with no params + reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in enum `S` +} + +impl X { + reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in enum `S` + reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in enum `S` + + fn get_s(self) -> S<'static, (), 1> { + panic!(); + } + + reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in enum `S` + reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in enum `S` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-enums.stderr b/tests/ui/delegation/inherent-impls-enums.stderr new file mode 100644 index 0000000000000..0016488386017 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-enums.stderr @@ -0,0 +1,159 @@ +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:13:28 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:15:28 + | +LL | reuse S::<'static, (), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:17:28 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:20:28 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:22:28 + | +LL | reuse S::<'static, (), 1>::foo_self as foo_self_3; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:24:28 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:31:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:33:32 + | +LL | reuse S::<'static, (), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:35:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:42:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:44:32 + | +LL | reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:46:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:53:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:55:32 + | +LL | reuse S::<'static, (), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:57:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:60:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:63:32 + | +LL | reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:66:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:71:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:73:32 + | +LL | reuse S::<'static, (), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in enum `S` + --> $DIR/inherent-impls-enums.rs:75:32 + | +LL | reuse S::<'static, (), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:82:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:84:32 + | +LL | reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in enum `S` + --> $DIR/inherent-impls-enums.rs:86:32 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-enums.rs:60:76 + | +LL | reuse S::<'static, (), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^^^^^^^^^ + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-enums.rs:63:55 + | +LL | reuse S::<'static, (), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 26 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-glob-list.rs b/tests/ui/delegation/inherent-impls-glob-list.rs new file mode 100644 index 0000000000000..51d24e6838df0 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-glob-list.rs @@ -0,0 +1,23 @@ +#![feature(fn_delegation)] + +struct X; + +impl X { + fn foo(&self) {} + fn foo2(&self) {} +} + +struct Y; + +impl Y { + reuse X::{foo, foo2} { X } + //~^ ERROR: cannot find function `foo` in `X` + //~| ERROR: cannot find function `foo2` in `X` +} + +impl Y { + reuse X::*; + //~^ ERROR: expected trait, found struct `X` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-glob-list.stderr b/tests/ui/delegation/inherent-impls-glob-list.stderr new file mode 100644 index 0000000000000..d2dfce86f860a --- /dev/null +++ b/tests/ui/delegation/inherent-impls-glob-list.stderr @@ -0,0 +1,21 @@ +error: expected trait, found struct `X` + --> $DIR/inherent-impls-glob-list.rs:19:11 + | +LL | reuse X::*; + | ^ not a trait + +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-glob-list.rs:13:15 + | +LL | reuse X::{foo, foo2} { X } + | ^^^ not found in `X` + +error[E0425]: cannot find function `foo2` in `X` + --> $DIR/inherent-impls-glob-list.rs:13:20 + | +LL | reuse X::{foo, foo2} { X } + | ^^^^ not found in `X` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-mixed-generics.rs b/tests/ui/delegation/inherent-impls-mixed-generics.rs new file mode 100644 index 0000000000000..027dcace3ab0d --- /dev/null +++ b/tests/ui/delegation/inherent-impls-mixed-generics.rs @@ -0,0 +1,16 @@ +#![feature(fn_delegation)] + +struct S<'a, A, B, const C: usize> { + xd: &'a [(A, B); C], +} + +impl<'a, 'b, 'c, A, const C: usize> S<'static, A, usize, C> { + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +trait Trait<'a, AA, BB> where Self: Sized { + reuse S::foo_self; + //~^ ERROR: cannot find function `foo_self` in `S` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-mixed-generics.stderr b/tests/ui/delegation/inherent-impls-mixed-generics.stderr new file mode 100644 index 0000000000000..f515b52ca45ab --- /dev/null +++ b/tests/ui/delegation/inherent-impls-mixed-generics.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-mixed-generics.rs:12:14 + | +LL | reuse S::foo_self; + | ^^^^^^^^ not found in `S` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-non-local-crate.rs b/tests/ui/delegation/inherent-impls-non-local-crate.rs new file mode 100644 index 0000000000000..c2f07fd8e7ae7 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-non-local-crate.rs @@ -0,0 +1,25 @@ +//@ aux-crate:inherent_impl=inherent-impl.rs + +#![feature(fn_delegation)] + +reuse inherent_impl::S::foo; +//~^ ERROR: cannot find function `foo` in `inherent_impl::S` + +reuse inherent_impl::S::not_existing; +//~^ ERROR: cannot find function `not_existing` in `inherent_impl::S` + +reuse inherent_impl::S::TYPE; +//~^ ERROR: cannot find function `TYPE` in `inherent_impl::S` + +reuse inherent_impl::S::CONST; +//~^ ERROR: cannot find function `CONST` in `inherent_impl::S` + +reuse inherent_impl::S::bar; +//~^ ERROR: cannot find function `bar` in `inherent_impl::S` + +reuse ::bar as trait_bar; + +reuse inherent_impl::X::foo as x_foo; +//~^ ERROR: cannot find function `foo` in `inherent_impl::X` + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-non-local-crate.stderr b/tests/ui/delegation/inherent-impls-non-local-crate.stderr new file mode 100644 index 0000000000000..983c8b8f3cb9b --- /dev/null +++ b/tests/ui/delegation/inherent-impls-non-local-crate.stderr @@ -0,0 +1,39 @@ +error[E0425]: cannot find function `foo` in `inherent_impl::S` + --> $DIR/inherent-impls-non-local-crate.rs:5:25 + | +LL | reuse inherent_impl::S::foo; + | ^^^ not found in `inherent_impl::S` + +error[E0425]: cannot find function `not_existing` in `inherent_impl::S` + --> $DIR/inherent-impls-non-local-crate.rs:8:25 + | +LL | reuse inherent_impl::S::not_existing; + | ^^^^^^^^^^^^ not found in `inherent_impl::S` + +error[E0425]: cannot find function `TYPE` in `inherent_impl::S` + --> $DIR/inherent-impls-non-local-crate.rs:11:25 + | +LL | reuse inherent_impl::S::TYPE; + | ^^^^ not found in `inherent_impl::S` + +error[E0425]: cannot find function `CONST` in `inherent_impl::S` + --> $DIR/inherent-impls-non-local-crate.rs:14:25 + | +LL | reuse inherent_impl::S::CONST; + | ^^^^^ not found in `inherent_impl::S` + +error[E0425]: cannot find function `bar` in `inherent_impl::S` + --> $DIR/inherent-impls-non-local-crate.rs:17:25 + | +LL | reuse inherent_impl::S::bar; + | ^^^ not found in `inherent_impl::S` + +error[E0425]: cannot find function `foo` in `inherent_impl::X` + --> $DIR/inherent-impls-non-local-crate.rs:22:25 + | +LL | reuse inherent_impl::X::foo as x_foo; + | ^^^ not found in `inherent_impl::X` + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-parent-generics.rs b/tests/ui/delegation/inherent-impls-parent-generics.rs new file mode 100644 index 0000000000000..0f95540643bd7 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-parent-generics.rs @@ -0,0 +1,80 @@ +#![feature(fn_delegation)] +#![allow(late_bound_lifetime_arguments)] + +enum E<'a: 'a, A: 'a, const C: usize> { + A(A), + B(&'a [A; C]), +} + +impl<'a, 'b, 'c, A: 'a, const C: usize> E<'a, A, C> { + fn foo_static<'d: 'd, 'e, T, const B: bool>() {} + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +reuse E::foo_static as e; +//~^ ERROR: cannot find function `foo_static` in enum `E` + +reuse E::foo_self as e1; +//~^ ERROR: cannot find function `foo_self` in enum `E` + +reuse E::foo_static::<'static, (), true> as e2; +//~^ ERROR: cannot find function `foo_static` in enum `E` + +reuse E::foo_self::<'static, (), true> as e3; +//~^ ERROR: cannot find function `foo_self` in enum `E` + +reuse E::<'static, (), 123>::foo_static as e4; +//~^ ERROR: cannot find function `foo_static` in enum `E` +reuse E::<'static, (), 123>::foo_self as e5; +//~^ ERROR: cannot find function `foo_self` in enum `E` + +reuse E::<'static, (), 123>::foo_static::<'static, (), true> as e6; +//~^ ERROR: cannot find function `foo_static` in enum `E` +reuse E::<'static, (), 123>::foo_self::<'static, (), true> as e7; +//~^ ERROR: cannot find function `foo_self` in enum `E` + +reuse E::<'_, (), _>::foo_static as e8; +//~^ ERROR: cannot find function `foo_static` in enum `E` + +reuse E::<'_, _, _>::foo_self as e9; +//~^ ERROR: cannot find function `foo_self` in enum `E` + +struct S { + xd: [A; C], +} + +impl<'a, 'b, 'c, A, const C: usize> S { + fn foo_static<'d: 'd, 'e, T, const B: bool>() {} + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +reuse S::foo_static as s; +//~^ ERROR: cannot find function `foo_static` in `S` + +reuse S::foo_self as s1; +//~^ ERROR: cannot find function `foo_self` in `S` + +reuse S::foo_static::<'static, (), true> as s2; +//~^ ERROR: cannot find function `foo_static` in `S` + +reuse S::foo_self::<'static, (), true> as s3; +//~^ ERROR: cannot find function `foo_self` in `S` + +reuse S::<(), 123>::foo_static as s4; +//~^ ERROR: cannot find function `foo_static` in `S` +reuse S::<(), 123>::foo_self as s5; +//~^ ERROR: cannot find function `foo_self` in `S` + +reuse S::<(), 123>::foo_static::<'static, (), true> as s6; +//~^ ERROR: cannot find function `foo_static` in `S` +reuse S::<(), 123>::foo_self::<'static, (), true> as s7; +//~^ ERROR: cannot find function `foo_self` in `S` + +reuse S::<(), _>::foo_static as s8; +//~^ ERROR: cannot find function `foo_static` in `S` + +reuse S::<_, 123>::foo_self as s9; +//~^ ERROR: cannot find function `foo_self` in `S` + + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-parent-generics.stderr b/tests/ui/delegation/inherent-impls-parent-generics.stderr new file mode 100644 index 0000000000000..508ca65316457 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-parent-generics.stderr @@ -0,0 +1,123 @@ +error[E0425]: cannot find function `foo_static` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:14:10 + | +LL | reuse E::foo_static as e; + | ^^^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_self` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:17:10 + | +LL | reuse E::foo_self as e1; + | ^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_static` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:20:10 + | +LL | reuse E::foo_static::<'static, (), true> as e2; + | ^^^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_self` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:23:10 + | +LL | reuse E::foo_self::<'static, (), true> as e3; + | ^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_static` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:26:30 + | +LL | reuse E::<'static, (), 123>::foo_static as e4; + | ^^^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_self` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:28:30 + | +LL | reuse E::<'static, (), 123>::foo_self as e5; + | ^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_static` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:31:30 + | +LL | reuse E::<'static, (), 123>::foo_static::<'static, (), true> as e6; + | ^^^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_self` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:33:30 + | +LL | reuse E::<'static, (), 123>::foo_self::<'static, (), true> as e7; + | ^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_static` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:36:23 + | +LL | reuse E::<'_, (), _>::foo_static as e8; + | ^^^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_self` in enum `E` + --> $DIR/inherent-impls-parent-generics.rs:39:22 + | +LL | reuse E::<'_, _, _>::foo_self as e9; + | ^^^^^^^^ not found in `E` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-parent-generics.rs:51:10 + | +LL | reuse S::foo_static as s; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-parent-generics.rs:54:10 + | +LL | reuse S::foo_self as s1; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-parent-generics.rs:57:10 + | +LL | reuse S::foo_static::<'static, (), true> as s2; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-parent-generics.rs:60:10 + | +LL | reuse S::foo_self::<'static, (), true> as s3; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-parent-generics.rs:63:21 + | +LL | reuse S::<(), 123>::foo_static as s4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-parent-generics.rs:65:21 + | +LL | reuse S::<(), 123>::foo_self as s5; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-parent-generics.rs:68:21 + | +LL | reuse S::<(), 123>::foo_static::<'static, (), true> as s6; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-parent-generics.rs:70:21 + | +LL | reuse S::<(), 123>::foo_self::<'static, (), true> as s7; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-parent-generics.rs:73:19 + | +LL | reuse S::<(), _>::foo_static as s8; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-parent-generics.rs:76:20 + | +LL | reuse S::<_, 123>::foo_self as s9; + | ^^^^^^^^ not found in `S` + +error: aborting due to 20 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-receiver-mapping.rs b/tests/ui/delegation/inherent-impls-receiver-mapping.rs new file mode 100644 index 0000000000000..05a9c350af746 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-receiver-mapping.rs @@ -0,0 +1,71 @@ +#![feature(fn_delegation)] + +mod receiver_mapping { + struct X; + + impl X { + fn static_f() {} + fn by_value(self) {} + fn by_ref(&self) {} + fn by_mut_ref(&mut self) {} + } + + struct Y; + + impl Y { + fn get_x(&self) -> X { X } + reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + //~^ ERROR: cannot find function `by_mut_ref` in `X` + //~| ERROR: cannot find function `by_ref` in `X` + //~| ERROR: cannot find function `by_value` in `X` + //~| ERROR: cannot find function `static_f` in `X` + } + + fn check() { + let y = Y; + y.by_ref(); + //~^ ERROR: no method named `by_ref` found for struct `Y` in the current scope + y.by_mut_ref(); + //~^ ERROR: no method named `by_mut_ref` found for struct `Y` in the current scope + y.by_value(); + //~^ ERROR: no method named `by_value` found for struct `Y` in the current scope + + let y = &Y; + y.by_value(); + //~^ ERROR: no method named `by_value` found for reference `&Y` in the current scope + y.by_ref(); + //~^ ERROR: no method named `by_ref` found for reference `&Y` in the current scope + y.by_mut_ref(); + //~^ ERROR: no method named `by_mut_ref` found for reference `&Y` in the current scope + + let y = &mut Y; + y.by_value(); + //~^ ERROR: no method named `by_value` found for mutable reference `&mut Y` in the current scope + y.by_ref(); + //~^ ERROR: the method `by_ref` exists for mutable reference `&mut Y`, but its trait bounds were not satisfied + y.by_mut_ref(); + //~^ ERROR: no method named `by_mut_ref` found for mutable reference `&mut Y` in the current scope + } +} + +mod self_type_mapping { + struct X; + impl X { + fn add(self, other: Self) -> Self { + Self + } + } + + struct W(X); + impl W { + reuse X::add { self.0 } + //~^ ERROR: cannot find function `add` in `X` + } + + fn check() { + W(X).add(W(X)); + //~^ ERROR: no method named `add` found for struct `W` in the current scope + } +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-receiver-mapping.stderr b/tests/ui/delegation/inherent-impls-receiver-mapping.stderr new file mode 100644 index 0000000000000..92baa5ff53ed0 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-receiver-mapping.stderr @@ -0,0 +1,256 @@ +error[E0425]: cannot find function `static_f` in `X` + --> $DIR/inherent-impls-receiver-mapping.rs:17:19 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^ not found in `X` + +error[E0425]: cannot find function `by_value` in `X` + --> $DIR/inherent-impls-receiver-mapping.rs:17:29 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^ not found in `X` + +error[E0425]: cannot find function `by_ref` in `X` + --> $DIR/inherent-impls-receiver-mapping.rs:17:39 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^ not found in `X` + +error[E0425]: cannot find function `by_mut_ref` in `X` + --> $DIR/inherent-impls-receiver-mapping.rs:17:47 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^^^ not found in `X` + +error[E0425]: cannot find function `add` in `X` + --> $DIR/inherent-impls-receiver-mapping.rs:61:18 + | +LL | reuse X::add { self.0 } + | ^^^ not found in `X` + +error[E0599]: no method named `by_ref` found for struct `Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:26:11 + | +LL | struct Y; + | -------- method `by_ref` not found for this struct +... +LL | y.by_ref(); + | ^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:39 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following traits define an item `by_ref`, perhaps you need to implement one of them: + candidate #1: `Iterator` + candidate #2: `std::io::Read` + candidate #3: `std::io::Write` +help: use associated function syntax instead + | +LL - y.by_ref(); +LL + Y::by_ref(); + | + +error[E0599]: no method named `by_mut_ref` found for struct `Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:28:11 + | +LL | struct Y; + | -------- method `by_mut_ref` not found for this struct +... +LL | y.by_mut_ref(); + | ^^^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:47 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_mut_ref(); +LL + Y::by_mut_ref(); + | + +error[E0599]: no method named `by_value` found for struct `Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:30:11 + | +LL | struct Y; + | -------- method `by_value` not found for this struct +... +LL | y.by_value(); + | ^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:29 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_value(); +LL + Y::by_value(); + | + +error[E0599]: no method named `by_value` found for reference `&Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:34:11 + | +LL | y.by_value(); + | ^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:29 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_value(); +LL + Y::by_value(); + | + +error[E0599]: no method named `by_ref` found for reference `&Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:36:11 + | +LL | y.by_ref(); + | ^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:39 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following traits define an item `by_ref`, perhaps you need to implement one of them: + candidate #1: `std::io::Read` + candidate #2: `std::io::Write` + = note: the trait `Iterator` defines an item `by_ref`, but is explicitly unimplemented +help: use associated function syntax instead + | +LL - y.by_ref(); +LL + Y::by_ref(); + | + +error[E0599]: no method named `by_mut_ref` found for reference `&Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:38:11 + | +LL | y.by_mut_ref(); + | ^^^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:47 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_mut_ref(); +LL + Y::by_mut_ref(); + | + +error[E0599]: no method named `by_value` found for mutable reference `&mut Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:42:11 + | +LL | y.by_value(); + | ^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:29 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_value(); +LL + Y::by_value(); + | + +error[E0599]: the method `by_ref` exists for mutable reference `&mut Y`, but its trait bounds were not satisfied + --> $DIR/inherent-impls-receiver-mapping.rs:44:11 + | +LL | struct Y; + | -------- doesn't satisfy `Y: Iterator` +... +LL | y.by_ref(); + | ^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:39 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^ + = note: the following trait bounds were not satisfied: + `Y: Iterator` + which is required by `&mut Y: Iterator` +note: the trait `Iterator` must be implemented + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following traits define an item `by_ref`, perhaps you need to implement one of them: + candidate #1: `std::io::Read` + candidate #2: `std::io::Write` + = note: the trait `Iterator` defines an item `by_ref`, but is explicitly unimplemented +help: use associated function syntax instead + | +LL - y.by_ref(); +LL + Y::by_ref(); + | + +error[E0599]: no method named `by_mut_ref` found for mutable reference `&mut Y` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:46:11 + | +LL | y.by_mut_ref(); + | ^^^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Y` + --> $DIR/inherent-impls-receiver-mapping.rs:17:47 + | +LL | reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() } + | ^^^^^^^^^^ +help: use associated function syntax instead + | +LL - y.by_mut_ref(); +LL + Y::by_mut_ref(); + | + +error[E0599]: no method named `add` found for struct `W` in the current scope + --> $DIR/inherent-impls-receiver-mapping.rs:66:14 + | +LL | struct W(X); + | -------- method `add` not found for this struct +... +LL | W(X).add(W(X)); + | ^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `W` + --> $DIR/inherent-impls-receiver-mapping.rs:61:18 + | +LL | reuse X::add { self.0 } + | ^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `add`, perhaps you need to implement it: + candidate #1: `Add` +help: use associated function syntax instead + | +LL - W(X).add(W(X)); +LL + W::add(W(X)); + | +help: one of the expressions' fields has a method of the same name + | +LL | W(X).0.add(W(X)); + | ++ + +error: aborting due to 15 previous errors + +Some errors have detailed explanations: E0425, E0599. +For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-recursive-cycle.rs b/tests/ui/delegation/inherent-impls-recursive-cycle.rs new file mode 100644 index 0000000000000..0860ff39f51ee --- /dev/null +++ b/tests/ui/delegation/inherent-impls-recursive-cycle.rs @@ -0,0 +1,52 @@ +#![feature(fn_delegation)] + +trait Trait1 { + reuse trait_foo_reused as foo; +} + +impl Trait1 for () {} + +struct S1(T); +impl S1 { + reuse Trait1::foo { self.0 } + //~^ ERROR: delegation's target expression is specified for function with no params + //~| ERROR: this function takes 0 arguments but 1 argument was supplied +} + +struct S2(S1<()>); +impl S2 { + reuse S1::<()>::foo { self.0 } + //~^ ERROR: cannot find function `foo` in `S1` +} + +reuse S2::foo; +//~^ ERROR: cannot find function `foo` in `S2` + +struct S3; +impl S3 { + reuse foo; +} + +impl Trait1 for S3 { + reuse S2::foo { S2(S1(())) } + //~^ ERROR: delegation's target expression is specified for function with no params + //~| ERROR: cannot find function `foo` in `S2` +} + +trait Trait2 { + reuse ::foo { S3 } + //~^ ERROR: delegation's target expression is specified for function with no params + //~| ERROR: this function takes 0 arguments but 1 argument was supplied +} + +reuse Trait2::foo as trait_foo; + +struct S4; +impl S4 { + reuse trait_foo; +} + +reuse S4::trait_foo as trait_foo_reused; +//~^ ERROR: cannot find function `trait_foo` in `S4` + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-recursive-cycle.stderr b/tests/ui/delegation/inherent-impls-recursive-cycle.stderr new file mode 100644 index 0000000000000..a68931f8abce7 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-recursive-cycle.stderr @@ -0,0 +1,80 @@ +error[E0425]: cannot find function `foo` in `S1` + --> $DIR/inherent-impls-recursive-cycle.rs:18:21 + | +LL | reuse S1::<()>::foo { self.0 } + | ^^^ not found in `S1` + +error[E0425]: cannot find function `foo` in `S2` + --> $DIR/inherent-impls-recursive-cycle.rs:22:11 + | +LL | reuse S2::foo; + | ^^^ not found in `S2` + +error[E0425]: cannot find function `foo` in `S2` + --> $DIR/inherent-impls-recursive-cycle.rs:31:15 + | +LL | reuse S2::foo { S2(S1(())) } + | ^^^ not found in `S2` + +error[E0425]: cannot find function `trait_foo` in `S4` + --> $DIR/inherent-impls-recursive-cycle.rs:49:11 + | +LL | reuse S4::trait_foo as trait_foo_reused; + | ^^^^^^^^^ not found in `S4` + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-recursive-cycle.rs:11:23 + | +LL | reuse Trait1::foo { self.0 } + | ^^^^^^^^^^ + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-recursive-cycle.rs:31:19 + | +LL | reuse S2::foo { S2(S1(())) } + | ^^^^^^^^^^^^^^ + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-recursive-cycle.rs:37:31 + | +LL | reuse ::foo { S3 } + | ^^^^^^ + +error[E0061]: this function takes 0 arguments but 1 argument was supplied + --> $DIR/inherent-impls-recursive-cycle.rs:11:19 + | +LL | reuse Trait1::foo { self.0 } + | ^^^ ---------- unexpected argument + | +note: associated function defined here + --> $DIR/inherent-impls-recursive-cycle.rs:4:31 + | +LL | reuse trait_foo_reused as foo; + | ^^^ +help: remove the extra argument + | +LL - reuse Trait1::foo { self.0 } +LL + reuse Trait1::fo{ self.0 } + | + +error[E0061]: this function takes 0 arguments but 1 argument was supplied + --> $DIR/inherent-impls-recursive-cycle.rs:37:27 + | +LL | reuse ::foo { S3 } + | ^^^ ------ unexpected argument of type `S3` + | +note: associated function defined here + --> $DIR/inherent-impls-recursive-cycle.rs:4:31 + | +LL | reuse trait_foo_reused as foo; + | ^^^ +help: remove the extra argument + | +LL - reuse ::foo { S3 } +LL + reuse ::fo{ S3 } + | + +error: aborting due to 9 previous errors + +Some errors have detailed explanations: E0061, E0425. +For more information about an error, try `rustc --explain E0061`. diff --git a/tests/ui/delegation/inherent-impls-recursive.rs b/tests/ui/delegation/inherent-impls-recursive.rs new file mode 100644 index 0000000000000..c57ef40642346 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-recursive.rs @@ -0,0 +1,69 @@ +#![feature(fn_delegation)] + +mod test_1 { + struct S1; + impl S1 { + fn foo() {} + } + + struct S2; + impl S2 { + reuse S1::foo; + //~^ ERROR: cannot find function `foo` in `S1` + } + + struct S3; + impl S3 { + reuse S2::foo; + //~^ ERROR: cannot find function `foo` in `S2` + } +} + +mod test_2 { + trait Trait1 { + fn foo(&self) {} + } + + impl Trait1 for () {} + + struct S1(T); + impl S1 { + reuse Trait1::foo { self.0 } + } + + struct S2(S1<()>); + impl S2 { + reuse S1::<()>::foo { self.0 } + //~^ ERROR: cannot find function `foo` in `S1` + } + + reuse S2::foo; + //~^ ERROR: cannot find function `foo` in `S2` + + struct S3; + impl S3 { + reuse foo; + } + + impl Trait1 for S3 { + reuse S2::foo { &S2(S1(())) } + //~^ ERROR: method `foo` has a `&self` declaration in the trait, but not in the impl + //~| ERROR: cannot find function `foo` in `S2` + } + + trait Trait2 { + reuse ::foo { S3 } + } + + reuse Trait2::foo as trait_foo; + + struct S4; + impl S4 { + reuse trait_foo; + } + + reuse S4::trait_foo as trait_foo_reused; + //~^ ERROR: cannot find function `trait_foo` in `S4` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-recursive.stderr b/tests/ui/delegation/inherent-impls-recursive.stderr new file mode 100644 index 0000000000000..f80d9d3b84097 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-recursive.stderr @@ -0,0 +1,61 @@ +error[E0425]: cannot find function `foo` in `S1` + --> $DIR/inherent-impls-recursive.rs:11:19 + | +LL | reuse S1::foo; + | ^^^ not found in `S1` + | +note: function `test_2::foo` exists but is inaccessible + --> $DIR/inherent-impls-recursive.rs:40:5 + | +LL | reuse S2::foo; + | ^^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo` in `S2` + --> $DIR/inherent-impls-recursive.rs:17:19 + | +LL | reuse S2::foo; + | ^^^ not found in `S2` + | +note: function `test_2::foo` exists but is inaccessible + --> $DIR/inherent-impls-recursive.rs:40:5 + | +LL | reuse S2::foo; + | ^^^^^^^^^^^^^^ not accessible + +error[E0425]: cannot find function `foo` in `S1` + --> $DIR/inherent-impls-recursive.rs:36:25 + | +LL | reuse S1::<()>::foo { self.0 } + | ^^^ not found in `S1` + +error[E0425]: cannot find function `foo` in `S2` + --> $DIR/inherent-impls-recursive.rs:40:15 + | +LL | reuse S2::foo; + | ^^^ not found in `S2` + +error[E0425]: cannot find function `foo` in `S2` + --> $DIR/inherent-impls-recursive.rs:49:19 + | +LL | reuse S2::foo { &S2(S1(())) } + | ^^^ not found in `S2` + +error[E0425]: cannot find function `trait_foo` in `S4` + --> $DIR/inherent-impls-recursive.rs:65:15 + | +LL | reuse S4::trait_foo as trait_foo_reused; + | ^^^^^^^^^ not found in `S4` + +error[E0186]: method `foo` has a `&self` declaration in the trait, but not in the impl + --> $DIR/inherent-impls-recursive.rs:49:19 + | +LL | fn foo(&self) {} + | ------------- `&self` used in trait +... +LL | reuse S2::foo { &S2(S1(())) } + | ^^^ expected `&self` in impl + +error: aborting due to 7 previous errors + +Some errors have detailed explanations: E0186, E0425. +For more information about an error, try `rustc --explain E0186`. diff --git a/tests/ui/delegation/inherent-impls-rename.rs b/tests/ui/delegation/inherent-impls-rename.rs new file mode 100644 index 0000000000000..7b6e1e4b7cddc --- /dev/null +++ b/tests/ui/delegation/inherent-impls-rename.rs @@ -0,0 +1,14 @@ +#![feature(fn_delegation)] + +struct X; + +fn foo() {} + +impl X { + reuse foo as bar; +} + +reuse X::bar; +//~^ ERROR: cannot find function `bar` in `X` + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-rename.stderr b/tests/ui/delegation/inherent-impls-rename.stderr new file mode 100644 index 0000000000000..e2e8946412e93 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-rename.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find function `bar` in `X` + --> $DIR/inherent-impls-rename.rs:11:10 + | +LL | reuse X::bar; + | ^^^ not found in `X` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-self-mapping.rs b/tests/ui/delegation/inherent-impls-self-mapping.rs new file mode 100644 index 0000000000000..36aef3a7bc7d8 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-self-mapping.rs @@ -0,0 +1,20 @@ +#![feature(fn_delegation)] + +use std::rc::Rc; + +struct X; +impl X { + fn foo(self: Rc>, other: Box>) -> Option> { + None + } +} + +trait Trait { + reuse X::foo; + //~^ ERROR: cannot find function `foo` in `X` +} + +reuse X::foo; +//~^ ERROR: cannot find function `foo` in `X` + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-self-mapping.stderr b/tests/ui/delegation/inherent-impls-self-mapping.stderr new file mode 100644 index 0000000000000..9ecf04626d29c --- /dev/null +++ b/tests/ui/delegation/inherent-impls-self-mapping.stderr @@ -0,0 +1,15 @@ +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-self-mapping.rs:13:14 + | +LL | reuse X::foo; + | ^^^ not found in `X` + +error[E0425]: cannot find function `foo` in `X` + --> $DIR/inherent-impls-self-mapping.rs:17:10 + | +LL | reuse X::foo; + | ^^^ not found in `X` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-self-replacement.rs b/tests/ui/delegation/inherent-impls-self-replacement.rs new file mode 100644 index 0000000000000..e6aa7458993fd --- /dev/null +++ b/tests/ui/delegation/inherent-impls-self-replacement.rs @@ -0,0 +1,56 @@ +#![feature(fn_delegation)] + +use std::rc::Rc; +use std::pin::Pin; + +struct S { + xd: [A; C], +} + +impl<'a, 'b, 'c, A, const C: usize> S { + fn by_value<'d: 'd, 'e, T, const B: bool>(self) {} + fn by_ref<'d: 'd, 'e, T, const B: bool>(&self) {} + fn by_mut_ref<'d: 'd, 'e, T, const B: bool>(&mut self) {} + fn by_box<'d: 'd, 'e, T, const B: bool>(self: Box) {} + fn by_rc<'d: 'd, 'e, T, const B: bool>(self: Rc) {} + fn by_pin<'d: 'd, 'e, T, const B: bool>(self: Pin>) {} +} + +trait Trait: Sized { + fn get_s(self) -> S<(), 123>; + + reuse S::<(), 123>::by_value { self.get_s() } + //~^ ERROR: cannot find function `by_value` in `S` + + reuse S::<(), 123>::by_ref { self.get_s() } + //~^ ERROR: cannot find function `by_ref` in `S` + + reuse S::<(), 123>::by_mut_ref { self.get_s() } + //~^ ERROR: cannot find function `by_mut_ref` in `S` + + reuse S::<(), 123>::by_box { self.get_s() } + //~^ ERROR: cannot find function `by_box` in `S` + + reuse S::<(), 123>::by_rc { self.get_s() } + //~^ ERROR: cannot find function `by_rc` in `S` + + reuse S::<(), 123>::by_pin { self.get_s() } + //~^ ERROR: cannot find function `by_pin` in `S` +} + +trait Trait2: Sized { + reuse S::<(), 123>::by_value { self.get_s() } + //~^ ERROR: cannot find function `by_value` in `S` + reuse S::<(), 123>::by_ref { self.get_s() } + //~^ ERROR: cannot find function `by_ref` in `S` + reuse S::<(), 123>::by_mut_ref { self.get_s() } + //~^ ERROR: cannot find function `by_mut_ref` in `S` + reuse S::<(), 123>::by_box { self.get_s() } + //~^ ERROR: cannot find function `by_box` in `S` + reuse S::<(), 123>::by_rc { self.get_s() } + //~^ ERROR: cannot find function `by_rc` in `S` + reuse S::<(), 123>::by_pin { self.get_s() } + //~^ ERROR: cannot find function `by_pin` in `S` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-self-replacement.stderr b/tests/ui/delegation/inherent-impls-self-replacement.stderr new file mode 100644 index 0000000000000..e293635e4a1ea --- /dev/null +++ b/tests/ui/delegation/inherent-impls-self-replacement.stderr @@ -0,0 +1,75 @@ +error[E0425]: cannot find function `by_value` in `S` + --> $DIR/inherent-impls-self-replacement.rs:22:25 + | +LL | reuse S::<(), 123>::by_value { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_ref` in `S` + --> $DIR/inherent-impls-self-replacement.rs:25:25 + | +LL | reuse S::<(), 123>::by_ref { self.get_s() } + | ^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_mut_ref` in `S` + --> $DIR/inherent-impls-self-replacement.rs:28:25 + | +LL | reuse S::<(), 123>::by_mut_ref { self.get_s() } + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_box` in `S` + --> $DIR/inherent-impls-self-replacement.rs:31:25 + | +LL | reuse S::<(), 123>::by_box { self.get_s() } + | ^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_rc` in `S` + --> $DIR/inherent-impls-self-replacement.rs:34:25 + | +LL | reuse S::<(), 123>::by_rc { self.get_s() } + | ^^^^^ not found in `S` + +error[E0425]: cannot find function `by_pin` in `S` + --> $DIR/inherent-impls-self-replacement.rs:37:25 + | +LL | reuse S::<(), 123>::by_pin { self.get_s() } + | ^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_value` in `S` + --> $DIR/inherent-impls-self-replacement.rs:42:25 + | +LL | reuse S::<(), 123>::by_value { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_ref` in `S` + --> $DIR/inherent-impls-self-replacement.rs:44:25 + | +LL | reuse S::<(), 123>::by_ref { self.get_s() } + | ^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_mut_ref` in `S` + --> $DIR/inherent-impls-self-replacement.rs:46:25 + | +LL | reuse S::<(), 123>::by_mut_ref { self.get_s() } + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_box` in `S` + --> $DIR/inherent-impls-self-replacement.rs:48:25 + | +LL | reuse S::<(), 123>::by_box { self.get_s() } + | ^^^^^^ not found in `S` + +error[E0425]: cannot find function `by_rc` in `S` + --> $DIR/inherent-impls-self-replacement.rs:50:25 + | +LL | reuse S::<(), 123>::by_rc { self.get_s() } + | ^^^^^ not found in `S` + +error[E0425]: cannot find function `by_pin` in `S` + --> $DIR/inherent-impls-self-replacement.rs:52:25 + | +LL | reuse S::<(), 123>::by_pin { self.get_s() } + | ^^^^^^ not found in `S` + +error: aborting due to 12 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-structs.rs b/tests/ui/delegation/inherent-impls-structs.rs new file mode 100644 index 0000000000000..a5950185b6dad --- /dev/null +++ b/tests/ui/delegation/inherent-impls-structs.rs @@ -0,0 +1,86 @@ +#![feature(fn_delegation)] + +struct S { + xd: [A; C], +} + +impl<'a, 'b, 'c, A, const C: usize> S { + fn foo_static<'d: 'd, 'e, T, const B: bool>() {} + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; +//~^ ERROR: cannot find function `foo_static` in `S` +reuse S::<(), 1>::foo_static as foo_static_3; +//~^ ERROR: cannot find function `foo_static` in `S` +reuse S::::foo_static::<'static, _, _> as foo_static_4; +//~^ ERROR: cannot find function `foo_static` in `S` + +reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1; +//~^ ERROR: cannot find function `foo_self` in `S` +reuse S::<(), 1>::foo_self as foo_self_3; +//~^ ERROR: cannot find function `foo_self` in `S` +reuse S::::foo_self::<'static, _, _> as foo_self_4; +//~^ ERROR: cannot find function `foo_self` in `S` + +trait Trait<'a, AA, BB> where Self: Sized { + reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in `S` + + fn get_s(self) -> S<(), 1> { + panic!(); + } + + reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in `S` +} + +struct X; + +impl<'a, A, B> Trait<'a, A, B> for X { + reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in `S` + + reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + //~| ERROR: delegation's target expression is specified for function with no params + reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + //~| ERROR: delegation's target expression is specified for function with no params + reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in `S` +} + +impl X { + reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static as foo_static_3; + //~^ ERROR: cannot find function `foo_static` in `S` + reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + //~^ ERROR: cannot find function `foo_static` in `S` + + fn get_s(self) -> S<(), 1> { + panic!(); + } + + reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + //~^ ERROR: cannot find function `foo_self` in `S` + reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + //~^ ERROR: cannot find function `foo_self` in `S` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-structs.stderr b/tests/ui/delegation/inherent-impls-structs.stderr new file mode 100644 index 0000000000000..6a426564a6e3a --- /dev/null +++ b/tests/ui/delegation/inherent-impls-structs.stderr @@ -0,0 +1,159 @@ +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:12:19 + | +LL | reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:14:19 + | +LL | reuse S::<(), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:16:22 + | +LL | reuse S::::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:19:19 + | +LL | reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:21:19 + | +LL | reuse S::<(), 1>::foo_self as foo_self_3; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:23:23 + | +LL | reuse S::::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:27:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:29:23 + | +LL | reuse S::<(), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:31:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:38:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:40:23 + | +LL | reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:42:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:49:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:51:23 + | +LL | reuse S::<(), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:53:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:56:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:59:23 + | +LL | reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:62:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:67:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, (), true> as foo_static_1; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:69:23 + | +LL | reuse S::<(), 1>::foo_static as foo_static_3; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_static` in `S` + --> $DIR/inherent-impls-structs.rs:71:23 + | +LL | reuse S::<(), 1>::foo_static::<'static, _, _> as foo_static_4; + | ^^^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:78:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:80:23 + | +LL | reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^ not found in `S` + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-structs.rs:82:23 + | +LL | reuse S::<(), 1>::foo_self::<'static, _, _> as foo_self_4; + | ^^^^^^^^ not found in `S` + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-structs.rs:56:67 + | +LL | reuse S::<(), 1>::foo_self::<'static, (), true> as foo_self_1 { self.get_s() } + | ^^^^^^^^^^^^^^^^ + +error: delegation's target expression is specified for function with no params + --> $DIR/inherent-impls-structs.rs:59:46 + | +LL | reuse S::<(), 1>::foo_self as foo_self_3 { self.get_s() } + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 26 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/delegation/inherent-impls-wrong-header-args-ice.rs b/tests/ui/delegation/inherent-impls-wrong-header-args-ice.rs new file mode 100644 index 0000000000000..8375df5f26587 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-wrong-header-args-ice.rs @@ -0,0 +1,17 @@ +#![feature(fn_delegation)] + +struct S<'a, A, const C: usize> { + xd: &'a [A; C], +} + +impl<'a, 'b, 'c, A, const C: usize> S { +//~^ ERROR: implicit elided lifetime not allowed here + fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {} +} + +trait Trait<'a, AA, BB> where Self: Sized { + reuse S::<(), ()>::foo_self; + //~^ ERROR: cannot find function `foo_self` in `S` +} + +fn main() {} diff --git a/tests/ui/delegation/inherent-impls-wrong-header-args-ice.stderr b/tests/ui/delegation/inherent-impls-wrong-header-args-ice.stderr new file mode 100644 index 0000000000000..bfa377dcc2db6 --- /dev/null +++ b/tests/ui/delegation/inherent-impls-wrong-header-args-ice.stderr @@ -0,0 +1,21 @@ +error[E0726]: implicit elided lifetime not allowed here + --> $DIR/inherent-impls-wrong-header-args-ice.rs:7:37 + | +LL | impl<'a, 'b, 'c, A, const C: usize> S { + | ^^^^^^^ expected lifetime parameter + | +help: indicate the anonymous lifetime + | +LL | impl<'a, 'b, 'c, A, const C: usize> S<'_, A, C> { + | +++ + +error[E0425]: cannot find function `foo_self` in `S` + --> $DIR/inherent-impls-wrong-header-args-ice.rs:13:24 + | +LL | reuse S::<(), ()>::foo_self; + | ^^^^^^^^ not found in `S` + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0425, E0726. +For more information about an error, try `rustc --explain E0425`.