diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 428f37b8af450..d8d8aab8b0cc7 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use std::hash::Hash; use std::ops::Range; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use std::{cmp, fmt, iter, mem}; use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; @@ -120,7 +120,7 @@ impl LazyAttrTokenStream { pub fn new_pending( start_token: (Token, Spacing), - cursor_snapshot: TokenCursor, + cursor_snapshot: FlatTokenCursor, num_calls: u32, break_last_token: u32, node_replacements: ThinVec, @@ -231,7 +231,7 @@ enum LazyAttrTokenStreamInner { // intermediate collection buffer to clone. Pending { start_token: (Token, Spacing), - cursor_snapshot: TokenCursor, + cursor_snapshot: FlatTokenCursor, num_calls: u32, break_last_token: u32, node_replacements: ThinVec, @@ -514,7 +514,7 @@ fn attrs_and_tokens_to_token_trees( for inner_attr in inner_attrs { tts.extend(inner_attr.token_trees()); } - tts.extend(stream.0.iter().cloned()); + tts.extend(stream.iter().cloned()); let stream = TokenStream::new(tts); *tree = TokenTree::Delimited(*span, *spacing, Delimiter::Brace, stream); return true; @@ -620,25 +620,157 @@ pub enum Spacing { JointHidden, } +/// The state of a view-backed [`TokenStream`]: a view of one nesting level +/// of a flat token buffer, plus its lazily materialized token trees. Boxed +/// in [`TokenStreamInner::Flat`] so the enum stays at `Vec` size (24 bytes, +/// niched on the box pointer): eager streams — the vast majority — carry no +/// dead `OnceLock`, and view streams pay one extra allocation each. +#[derive(Clone)] +pub(crate) struct FlatLazy { + view: FlatTokenSlice, + trees: OnceLock>, +} + +/// The backing of a [`TokenStream`]: either materialized token trees, or a +/// lazily materialized view of one nesting level of a flat token buffer. +/// The latter lets macro-invocation arguments flow from the parser to the +/// mbe matcher without the token tree ever being built; any tree-level +/// access materializes it on first use. +#[derive(Clone)] +pub(crate) enum TokenStreamInner { + Eager(Vec), + Flat(Box), +} + /// A `TokenStream` is an abstract sequence of tokens, organized into [`TokenTree`]s. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] -pub struct TokenStream(Arc>); +#[derive(Clone)] +pub struct TokenStream(pub(crate) Arc); + +// Manual impl printing the token trees in the same format as the old derived +// impl on `TokenStream(Arc>)`. For a flat view this +// materializes (and prints) only the viewed range — the derived impl would +// dump the whole underlying buffer for every view, which makes debug dumps +// of unexpanded macro calls quadratic in crate size. The materialized trees +// are cached in the stream's `OnceLock`, so formatting a view-backed stream +// populates its lazy state as a side effect. +impl fmt::Debug for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TokenStream").field(self.trees_vec()).finish() + } +} + +impl Default for TokenStream { + fn default() -> TokenStream { + TokenStream::new(Vec::new()) + } +} + +impl PartialEq for TokenStream { + fn eq(&self, other: &TokenStream) -> bool { + if Arc::ptr_eq(&self.0, &other.0) { + return true; + } + // Identical views of the same buffer are equal without materializing. + if let (Some(a), Some(b)) = (self.flat_view(), other.flat_view()) + && Arc::ptr_eq(&a.buf, &b.buf) + && a.start == b.start + && a.end == b.end + { + return true; + } + self.trees_vec() == other.trees_vec() + } +} + +impl Eq for TokenStream {} + +impl std::hash::Hash for TokenStream { + fn hash(&self, state: &mut H) { + self.trees_vec().hash(state); + } +} + +impl rustc_serialize::Encodable for TokenStream +where + Vec: rustc_serialize::Encodable, +{ + fn encode(&self, e: &mut E) { + self.trees_vec().encode(e); + } +} + +impl rustc_serialize::Decodable for TokenStream +where + Vec: rustc_serialize::Decodable, +{ + fn decode(d: &mut D) -> TokenStream { + TokenStream::new(rustc_serialize::Decodable::decode(d)) + } +} impl TokenStream { pub fn new(tts: Vec) -> TokenStream { - TokenStream(Arc::new(tts)) + TokenStream(Arc::new(TokenStreamInner::Eager(tts))) + } + + /// Creates a stream that is a lazily materialized view of a flat buffer + /// range covering one nesting level. + pub fn from_flat_view(view: FlatTokenSlice) -> TokenStream { + TokenStream(Arc::new(TokenStreamInner::Flat(Box::new(FlatLazy { + view, + trees: OnceLock::new(), + })))) + } + + /// The flat view backing this stream, if it has one (and tree access + /// would thus require materialization). + pub fn flat_view(&self) -> Option<&FlatTokenSlice> { + match &*self.0 { + TokenStreamInner::Flat(lazy) => Some(&lazy.view), + TokenStreamInner::Eager(_) => None, + } + } + + /// The materialized token trees, materializing a flat view on first use. + /// + /// For a view shared across threads (`-Zthreads`), concurrent first + /// calls may each compute the trees, with all but the `OnceLock` winner + /// discarded; the result is the same either way. + fn trees_vec(&self) -> &Vec { + match &*self.0 { + TokenStreamInner::Eager(trees) => trees, + TokenStreamInner::Flat(lazy) => lazy.trees.get_or_init(|| lazy.view.to_tree_vec()), + } + } + + /// Mutable access to the trees, converting a flat view into an eager + /// stream first. The view case copies twice (materialize, then clone + /// into the new `Eager` allocation); mutation of view-backed streams is + /// rare enough that this has not been worth a dedicated path. + fn vec_mut(&mut self) -> &mut Vec { + if let TokenStreamInner::Flat(..) = &*self.0 { + let trees = self.trees_vec().clone(); + self.0 = Arc::new(TokenStreamInner::Eager(trees)); + } + match Arc::make_mut(&mut self.0) { + TokenStreamInner::Eager(trees) => trees, + TokenStreamInner::Flat(..) => unreachable!(), + } } pub fn is_empty(&self) -> bool { - self.0.is_empty() + match &*self.0 { + TokenStreamInner::Eager(trees) => trees.is_empty(), + TokenStreamInner::Flat(lazy) => lazy.view.len() == 0, + } } pub fn len(&self) -> usize { - self.0.len() + self.trees_vec().len() } pub fn get(&self, index: usize) -> Option<&TokenTree> { - self.0.get(index) + self.trees_vec().get(index) } pub fn iter(&self) -> TokenStreamIter<'_> { @@ -682,7 +814,7 @@ impl TokenStream { /// construction within the compiler just build a `Vec` with /// normal `Vec` operations and then do `TokenStream::new`. pub fn push_tree_with_gluing(&mut self, tt: TokenTree) { - let vec_mut = Arc::make_mut(&mut self.0); + let vec_mut = self.vec_mut(); if Self::try_glue_to_last(vec_mut, &tt) { // nothing else to do @@ -699,11 +831,12 @@ impl TokenStream { /// construction within the compiler just build a `Vec` with /// normal `Vec` operations and then do `TokenStream::new`. pub fn push_stream_with_gluing(&mut self, stream: TokenStream) { - let vec_mut = Arc::make_mut(&mut self.0); + let vec_mut = self.vec_mut(); - let stream_iter = stream.0.iter().cloned(); + let stream_trees = stream.trees_vec(); + let stream_iter = stream_trees.iter().cloned(); - if let Some(first) = stream.0.first() + if let Some(first) = stream_trees.first() && Self::try_glue_to_last(vec_mut, first) { // Now skip the first token tree from `stream`. @@ -726,7 +859,7 @@ impl TokenStream { fn desugar_inner(mut stream: TokenStream) -> Option { let mut i = 0; let mut modified = false; - while let Some(tt) = stream.0.get(i) { + while let Some(tt) = stream.get(i) { match tt { &TokenTree::Token( Token { kind: token::DocComment(_, attr_style, data), span }, @@ -734,7 +867,7 @@ impl TokenStream { ) => { let desugared = desugared_tts(attr_style, data, span); let desugared_len = desugared.len(); - Arc::make_mut(&mut stream.0).splice(i..i + 1, desugared); + stream.vec_mut().splice(i..i + 1, desugared); modified = true; i += desugared_len; } @@ -745,7 +878,7 @@ impl TokenStream { if let Some(desugared_delim_stream) = desugar_inner(delim_stream.clone()) { let new_tt = TokenTree::Delimited(sp, spacing, delim, desugared_delim_stream); - Arc::make_mut(&mut stream.0)[i] = new_tt; + stream.vec_mut()[i] = new_tt; modified = true; } i += 1; @@ -807,7 +940,7 @@ impl TokenStream { pub fn add_comma(&self) -> Option<(TokenStream, Span)> { // Used to suggest if a user writes `foo!(a b);` let mut suggestion = None; - let mut iter = self.0.iter().enumerate().peekable(); + let mut iter = self.trees_vec().iter().enumerate().peekable(); while let Some((pos, ts)) = iter.next() { if let Some((_, next)) = iter.peek() { let sp = match (&ts, &next) { @@ -829,8 +962,9 @@ impl TokenStream { } } if let Some((pos, comma, sp)) = suggestion { - let mut new_stream = Vec::with_capacity(self.0.len() + 1); - let parts = self.0.split_at(pos + 1); + let trees = self.trees_vec(); + let mut new_stream = Vec::with_capacity(trees.len() + 1); + let parts = trees.split_at(pos + 1); new_stream.extend_from_slice(parts.0); new_stream.push(comma); new_stream.extend_from_slice(parts.1); @@ -848,23 +982,33 @@ impl FromIterator for TokenStream { impl StableHash for TokenStream { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { - self.0.as_slice().stable_hash(hcx, hasher); + // Hash as a slice so the tree count prefixes the elements: without + // it, nested-stream boundaries are ambiguous and different streams + // could produce identical incremental fingerprints. + self.trees_vec().as_slice().stable_hash(hcx, hasher); } } +/// Iterates over the trees of a stream. Holds the materialized tree slice +/// directly (materializing a flat view once at construction), so `next` is a +/// plain slice index rather than a per-element re-resolution of the stream's +/// backing. #[derive(Clone)] -pub struct TokenStreamIter<'t>(std::slice::Iter<'t, TokenTree>); +pub struct TokenStreamIter<'t> { + trees: &'t [TokenTree], + index: usize, +} impl<'t> TokenStreamIter<'t> { fn new(stream: &'t TokenStream) -> Self { - TokenStreamIter(stream.0.as_slice().iter()) + TokenStreamIter { trees: stream.trees_vec(), index: 0 } } // Peeking could be done via `Peekable`, but most iterators need peeking, // and this is simple and avoids the need to use `peekable` and `Peekable` // at all the use sites. pub fn peek(&self) -> Option<&'t TokenTree> { - self.0.as_slice().first() + self.trees.get(self.index) } } @@ -872,12 +1016,723 @@ impl<'t> Iterator for TokenStreamIter<'t> { type Item = &'t TokenTree; fn next(&mut self) -> Option<&'t TokenTree> { - self.0.next() + self.trees.get(self.index).map(|tree| { + self.index += 1; + tree + }) } fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() + // `index` only advances on a successful `next`, so it never exceeds + // the slice length. + let remaining = self.trees.len() - self.index; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for TokenStreamIter<'_> {} + +impl std::iter::FusedIterator for TokenStreamIter<'_> {} + +/// One entry of a [`FlatTokenCursor`]'s pre-flattened token buffer. +/// +/// The buffer contains every token the tree-walking cursor would synthesize, +/// in order, plus open/close entries for *skipped* invisible delimiters: +/// those entries are filtered out by [`FlatTokenCursor::inlined_next`], but +/// retaining them preserves the tree structure for tree-level lookahead and +/// depth queries. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct FlatEntry { + token: Token, + spacing: Spacing, + /// The nesting depth this entry lives at. Open-delimiter entries carry + /// the *parent* depth, while the contents and the close-delimiter entry + /// carry the inner depth. This makes [`FlatTokenCursor::depth`] agree + /// with the `stack.len()` of the old tree-walking cursor at every point + /// in the token sequence. + depth: u32, +} + +impl FlatEntry { + pub fn token(&self) -> &Token { + &self.token } + + pub fn spacing(&self) -> Spacing { + self.spacing + } +} + +/// The backing store of a flat token buffer: the entry sequence plus the +/// open-to-close match table, behind a single `Arc` so that cursors and +/// slices clone with one reference-count bump. The match table is kept out +/// of [`FlatEntry`] deliberately: it is consulted only at open-delimiter +/// entries, and inlining it would widen every entry of the sequentially +/// scanned buffer (though it does cost 4 bytes per entry itself; see the +/// comment on `matches`). +/// +/// Kept private to this module: the invariants (depths, match table) are +/// maintained solely by [`FlatSink`], and all consumers go through cursor +/// and slice methods. +struct FlatBuffer { + entries: Vec, + /// For every open-delimiter entry, the index of its matching + /// close-delimiter entry (zero for other entries). Lets the parser skip + /// a whole delimited sequence in one step, at the cost of one `u32` per + /// entry (+12.5% on the 32-byte entries). + matches: Vec, +} + +/// A linear cursor over a pre-flattened token stream, replacing the +/// tree-walking `TokenCursor`: advancing is an index increment, lookahead is +/// direct indexing, and cloning (for parser snapshots and lazy token stream +/// replay) is one reference-count bump. +#[derive(Clone)] +pub struct FlatTokenCursor { + buf: Arc, + /// Index of the next entry to consume. Invariant: `index <= end`. + index: u32, + /// Exclusive end of the entry range this cursor may consume. Equal to + /// `entries.len()` except for cursors over a sub-range of a buffer + /// (macro-invocation arguments), which yield `Eof` at the range end. + end: u32, +} + +// Manual impl: the derived one would print the whole underlying buffer +// (which can be an entire crate's tokens). +impl fmt::Debug for FlatTokenCursor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlatTokenCursor") + .field("index", &self.index) + .field("end", &self.end) + .field( + "entries", + &&self.buf.entries[self.index.min(self.end) as usize..self.end as usize], + ) + .finish() + } +} + +/// A view of one contiguous entry range of a flat token buffer — either a +/// whole delimited group (open and close entries included) or a run of +/// entries at one nesting level. Cloning is one reference-count bump; this +/// is the flat analog of an `Arc`-shared subtree. +#[derive(Clone)] +pub struct FlatTokenSlice { + buf: Arc, + /// Invariant: `start <= end <= buf.entries.len()`. + start: u32, + end: u32, +} + +// Manual impl: the derived one would print the whole underlying buffer +// (which can be an entire crate's tokens) for every view. +impl fmt::Debug for FlatTokenSlice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlatTokenSlice") + .field("start", &self.start) + .field("end", &self.end) + .field("entries", &self.entries()) + .finish() + } +} + +impl FlatTokenSlice { + fn new(buf: Arc, start: u32, end: u32) -> FlatTokenSlice { + debug_assert!(start <= end && end as usize <= buf.entries.len()); + FlatTokenSlice { buf, start, end } + } + + pub fn len(&self) -> usize { + (self.end - self.start) as usize + } + + pub fn entries(&self) -> &[FlatEntry] { + &self.buf.entries[self.start as usize..self.end as usize] + } + + /// The view of this slice's contents, without the open and close + /// delimiter entries. Requires the slice to be a whole delimited group. + pub fn inner_view(&self) -> FlatTokenSlice { + debug_assert!(self.buf.entries[self.start as usize].token.kind.open_delim().is_some()); + debug_assert!(self.len() >= 2); + FlatTokenSlice::new(Arc::clone(&self.buf), self.start + 1, self.end - 1) + } + + /// Rebuilds this slice as a token tree. Requires the slice to be a whole + /// delimited group (as produced by tt-fragment capture). + pub fn to_token_tree(&self) -> TokenTree { + debug_assert!(self.buf.entries[self.start as usize].token.kind.open_delim().is_some()); + flat_delimited_at(&self.buf.entries, &self.buf.matches, self.start as usize) + } + + pub fn to_token_stream(&self) -> TokenStream { + flat_range_to_stream( + &self.buf.entries, + &self.buf.matches, + self.start as usize, + self.end as usize, + ) + } + + /// Materializes the token trees of this slice, which must cover one + /// whole nesting level. + pub fn to_tree_vec(&self) -> Vec { + flat_range_to_trees( + &self.buf.entries, + &self.buf.matches, + self.start as usize, + self.end as usize, + ) + } +} + +/// A captured `tt` metavariable fragment in flat form: the flat analog of +/// the `TokenTree` the old cursor captured by `Arc`-cloning. +#[derive(Clone, Debug)] +pub enum FlatTt { + /// A whole delimited group, shared as a slice of its source buffer. + Slice(FlatTokenSlice), + /// A single non-delimited token, kept by value: it may be an unglued + /// half of a glued buffer entry, which no slice can represent. + Token(Token, Spacing), +} + +impl FlatTt { + pub fn to_token_tree(&self) -> TokenTree { + match self { + FlatTt::Slice(slice) => slice.to_token_tree(), + FlatTt::Token(token, spacing) => TokenTree::Token(*token, *spacing), + } + } +} + +/// An append-only builder for flat token buffers: the flat analog of +/// building a `TokenStream`. Delimited groups are emitted as an open entry, +/// contents, and a close entry (patching the match table); whole slices and +/// token trees can be spliced in with their depths and match indices rebased. +#[derive(Default)] +pub struct FlatSink { + entries: Vec, + matches: Vec, + /// Entry indices of currently open delimiters. + open_stack: Vec, +} + +// Manual impl: a sink mid-transcription holds an entire expansion's tokens; +// print a summary instead of dumping them. +impl fmt::Debug for FlatSink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlatSink") + .field("entries", &self.entries.len()) + .field("depth", &self.open_stack.len()) + .finish() + } +} + +impl FlatSink { + pub fn new() -> FlatSink { + FlatSink::default() + } + + pub fn with_capacity(cap: usize) -> FlatSink { + FlatSink { + entries: Vec::with_capacity(cap), + matches: Vec::with_capacity(cap), + open_stack: Vec::new(), + } + } + + /// The nesting depth entries pushed right now will carry. + pub fn depth(&self) -> u32 { + self.open_stack.len() as u32 + } + + #[inline] + pub fn push_token(&mut self, token: Token, spacing: Spacing) { + debug_assert!(!token.kind.is_delim()); + self.entries.push(FlatEntry { token, spacing, depth: self.depth() }); + self.matches.push(0); + } + + /// The number of entries emitted so far. + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Emits an open-delimiter entry and enters the group. `token` must be an + /// open-delimiter token. Returns the entry index of the open delimiter, + /// usable with [`FlatSink::patch_open_spacing`]. + pub fn open_delim(&mut self, token: Token, spacing: Spacing) -> usize { + debug_assert!(token.kind.open_delim().is_some()); + let open_idx = self.entries.len() as u32; + self.entries.push(FlatEntry { token, spacing, depth: self.depth() }); + self.matches.push(0); + self.open_stack.push(open_idx); + open_idx as usize + } + + /// Rewrites the spacing of the open-delimiter entry at `open_idx`. The + /// lexer only knows an open delimiter's spacing after bumping past it, + /// so it emits the entry with a placeholder and patches it here. + pub fn patch_open_spacing(&mut self, open_idx: usize, spacing: Spacing) { + debug_assert!(self.entries[open_idx].token.kind.open_delim().is_some()); + self.entries[open_idx].spacing = spacing; + } + + /// Emits the close-delimiter entry for the innermost open group and + /// leaves it. `token` must be the matching close-delimiter token. + pub fn close_delim(&mut self, token: Token, spacing: Spacing) { + debug_assert!(token.kind.close_delim().is_some()); + let open_idx = + self.open_stack.last().copied().expect("close_delim without a matching open_delim") + as usize; + self.matches[open_idx] = self.entries.len() as u32; + // The close entry carries the inner depth, like the contents. + self.entries.push(FlatEntry { token, spacing, depth: self.depth() }); + self.matches.push(0); + self.open_stack.pop(); + } + + /// Appends a copy of `slice`, rebasing entry depths and match indices to + /// this sink. Returns the entry index range the slice landed at. + pub fn splice_slice(&mut self, slice: &FlatTokenSlice) -> (usize, usize) { + // The depth rebase below keys off the first entry, so the slice must + // start at its own base depth: a whole group (open entry) or a run + // starting at the level the slice covers. + debug_assert!(slice.entries().first().is_none_or(|e| e.token.kind.close_delim().is_none())); + let dst_start = self.entries.len(); + let src = &slice.buf.entries[slice.start as usize..slice.end as usize]; + let src_matches = &slice.buf.matches[slice.start as usize..slice.end as usize]; + // The first entry's depth is the slice's base depth: for a whole + // delimited group that is the open entry, which carries the parent + // depth in the source buffer. + let depth_delta = self.depth() as i64 - src.first().map_or(0, |e| e.depth) as i64; + let idx_delta = dst_start as i64 - slice.start as i64; + self.entries.extend(src.iter().map(|e| FlatEntry { + token: e.token, + spacing: e.spacing, + depth: (e.depth as i64 + depth_delta) as u32, + })); + self.matches.extend(src_matches.iter().map(|&m| { + // Only open-delimiter entries carry a (nonzero) match index. + if m == 0 { 0 } else { (m as i64 + idx_delta) as u32 } + })); + (dst_start, self.entries.len()) + } + + /// Rewrites the spans of the boundary (first and last) entries of the + /// range returned by [`FlatSink::splice_slice`]. Used by transcription to + /// re-attribute a spliced group's delimiters to the metavariable span. + pub fn set_boundary_spans(&mut self, (start, end): (usize, usize), open: Span, close: Span) { + debug_assert!(start < end && end == self.entries.len()); + self.entries[start].token.span = open; + self.entries[end - 1].token.span = close; + } + + /// Appends the flattened form of a token stream, exactly as + /// [`FlatTokenCursor::new`] would produce it, at the current depth. + pub fn splice_stream(&mut self, stream: &TokenStream) { + let base = self.depth(); + // Iterative traversal; each stack element is the parent stream, the + // index of the tree *after* the `Delimited` we descended into, and + // the entry index of the open delimiter. + let mut stack: Vec<(TokenStream, usize, usize)> = Vec::new(); + let mut stream = stream.clone(); + let mut i = 0; + loop { + if let Some(tree) = stream.get(i) { + i += 1; + match tree { + &TokenTree::Token(token, spacing) => { + debug_assert!(!token.kind.is_delim()); + self.entries.push(FlatEntry { + token, + spacing, + depth: base + stack.len() as u32, + }); + self.matches.push(0); + } + &TokenTree::Delimited(sp, spacing, delim, ref tts) => { + let open_idx = self.entries.len(); + self.entries.push(FlatEntry { + token: Token::new(delim.as_open_token_kind(), sp.open), + spacing: spacing.open, + depth: base + stack.len() as u32, + }); + self.matches.push(0); + let tts = tts.clone(); + stack.push((mem::replace(&mut stream, tts), i, open_idx)); + i = 0; + } + } + } else if let Some((parent, parent_i, open_idx)) = stack.pop() { + let Some(&TokenTree::Delimited(sp, spacing, delim, _)) = parent.get(parent_i - 1) + else { + unreachable!("parent tree should be Delimited") + }; + self.matches[open_idx] = self.entries.len() as u32; + self.entries.push(FlatEntry { + token: Token::new(delim.as_close_token_kind(), sp.close), + spacing: spacing.close, + depth: base + stack.len() as u32 + 1, + }); + self.matches.push(0); + stream = parent; + i = parent_i; + } else { + break; + } + } + } + + /// Finishes the buffer. All opened delimiters must have been closed. + pub fn finish(self) -> FlatTokenCursor { + debug_assert!(self.open_stack.is_empty()); + FlatTokenCursor::from_parts(self.entries, self.matches) + } +} + +impl FlatTokenCursor { + pub fn new(stream: TokenStream) -> FlatTokenCursor { + let mut sink = FlatSink::new(); + sink.splice_stream(&stream); + sink.finish() + } + + /// Assembles a cursor from a pre-built buffer. Private: buffers are only + /// produced by [`FlatSink`], which maintains the depth and match-table + /// invariants; external producers go through the sink. + fn from_parts(mut entries: Vec, mut matches: Vec) -> FlatTokenCursor { + // Expansion output is not bounded by the source-file size limit, so + // the 32-bit entry indices need a real guard: a silent wrap would + // corrupt the match table. + let end = u32::try_from(entries.len()).expect("flat token buffer exceeds u32::MAX entries"); + assert_eq!(entries.len(), matches.len()); + debug_assert!(flat_buffer_is_well_formed(&entries, &matches)); + // Presize estimates can overshoot (comment- and string-heavy files), + // and captured views can pin the buffer for a long time; return + // large slack allocations rather than retaining them. The len/4 + // threshold keeps overshoot up to the ~p75 of measured source + // density (see the presize comment in `rustc_parse::lexer`) without + // a shrink copy; the 4096 floor exempts small buffers, where slack + // is cheaper than any copy. + if entries.capacity() - entries.len() > 4096 + entries.len() / 4 { + entries.shrink_to_fit(); + matches.shrink_to_fit(); + } + FlatTokenCursor { buf: Arc::new(FlatBuffer { entries, matches }), index: 0, end } + } + + /// A cursor over the sub-range of a buffer covered by `view`, sharing + /// the backing buffer. The cursor yields `Eof` at the range end. + pub fn from_view(view: &FlatTokenSlice) -> FlatTokenCursor { + FlatTokenCursor { buf: Arc::clone(&view.buf), index: view.start, end: view.end } + } + + /// Rebuilds the token *tree* for the remaining (unconsumed) range, for + /// the few consumers that need a `TokenStream` rather than a parser + /// (e.g. the proc-macro server's `from_str`). + pub fn to_token_stream(&self) -> TokenStream { + flat_range_to_stream( + &self.buf.entries, + &self.buf.matches, + self.index as usize, + self.end as usize, + ) + } + + pub fn next(&mut self) -> (Token, Spacing) { + self.inlined_next() + } + + /// This always-inlined version should only be used on hot code paths. + #[inline(always)] + pub fn inlined_next(&mut self) -> (Token, Spacing) { + while let Some(entry) = + self.buf.entries.get(self.index as usize).filter(|_| self.index < self.end) + { + self.index += 1; + if let token::OpenInvisible(origin) | token::CloseInvisible(origin) = entry.token.kind + && origin.skip() + { + continue; + } + return (entry.token, entry.spacing); + } + // We have exhausted the token stream. The use of `Spacing::Alone` is + // arbitrary and immaterial, because the `Eof` token's spacing is + // never used. + (Token::new(token::Eof, DUMMY_SP), Spacing::Alone) + } + + /// The nesting depth at the current position. For a full-buffer cursor + /// this agrees with the `stack.len()` of the old tree-walking cursor. + /// + /// CAVEAT for bounded sub-range cursors: depths are the *origin* + /// buffer's (macro-argument views start at the invocation's depth, not + /// 0), and past the range end this returns 0. Only compare depths taken + /// from the same cursor, and never across a possible range end — use + /// entry positions there instead. + pub fn depth(&self) -> usize { + if self.index < self.end { + self.buf.entries.get(self.index as usize).map_or(0, |e| e.depth as usize) + } else { + 0 + } + } + + /// The `dist`-th (one-based) upcoming token, not counting skipped + /// invisible delimiters, without consuming anything. Returns `Eof` past + /// the end of the stream. + pub fn peek(&self, dist: usize) -> Token { + debug_assert!(dist >= 1); + let mut remaining = dist; + for entry in self.buf.entries[self.index.min(self.end) as usize..self.end as usize].iter() { + if let token::OpenInvisible(origin) | token::CloseInvisible(origin) = entry.token.kind + && origin.skip() + { + continue; + } + if remaining <= 1 { + return entry.token; + } + remaining -= 1; + } + Token::new(token::Eof, DUMMY_SP) + } + + /// The delimiter of the innermost delimited sequence containing the + /// current position, or `None` in the outermost stream. + pub fn enclosing_delimiter(&self) -> Option { + // Every entry carries its nesting depth, so depth 0 (or the range + // end) means "no enclosing delimiter" without any scan. Note this + // fast path only fires for full-buffer cursors: a bounded view + // cursor carries the origin buffer's depths, which are nonzero even + // at the view's own top level, so it takes the scan below (bounded + // by the view's range end). + if self.depth() == 0 { + return None; + } + let entries = &self.buf.entries; + let end = self.end as usize; + let mut i = self.index as usize; + while i < end { + let entry = &entries[i]; + if entry.token.kind.open_delim().is_some() { + // Step over whole sibling groups via the match table. + let close_idx = self.buf.matches[i] as usize; + debug_assert!(close_idx > i); + i = close_idx + 1; + } else if let Some(delim) = entry.token.kind.close_delim() { + return Some(delim); + } else { + i += 1; + } + } + None + } + + /// The first token after the close delimiter of the innermost delimited + /// sequence containing the current position, provided it is a normal + /// (non-delimiter) token. Nested groups are stepped over via the match + /// table. + /// + /// This must walk from the raw cursor position: a skipped invisible + /// *open* right at the cursor still has its close ahead of us, so + /// filtering it out (as consumption does) would pair the walk one + /// nesting level too deep. + pub fn token_after_enclosing_close(&self) -> Option { + let entries = &self.buf.entries; + let end = self.end as usize; + let mut i = self.index as usize; + while i < end { + let entry = &entries[i]; + if entry.token.kind.open_delim().is_some() { + let close_idx = self.buf.matches[i] as usize; + debug_assert!(close_idx > i); + i = close_idx + 1; + } else if entry.token.kind.close_delim().is_some() { + let after = entries.get(i + 1).filter(|_| i + 1 < end)?; + // Match only normal tokens, like the tree-level lookahead of + // the old cursor (the following tree had to be a `Token`). + return (after.token.kind.open_delim().is_none() + && after.token.kind.close_delim().is_none()) + .then_some(after.token); + } else { + i += 1; + } + } + None + } + + /// The `dist`-th (one-based) upcoming whole element — token or delimited + /// group, including non-consumed invisible ones — at the current nesting + /// level, without consuming anything. A delimited group is returned with + /// a lazy view of its contents. Returns `None` if the current level ends + /// before `dist` elements, matching tree-level lookahead on the old + /// cursor. Like [`FlatTokenCursor::token_after_enclosing_close`], this + /// deliberately walks from the raw cursor position so that a skipped + /// invisible group right at the cursor counts as one element rather than + /// being entered transparently. + /// + /// A delimited result allocates (the returned tree owns its view + /// stream); all current callers are cold recovery/lookahead paths, so + /// this has not been worth a by-parts return type. + pub fn look_ahead_tree(&self, dist: usize) -> Option { + debug_assert!(dist >= 1); + let entries = &self.buf.entries; + let end = self.end as usize; + let mut i = self.index as usize; + let mut remaining = dist; + loop { + let entry = entries.get(i).filter(|_| i < end)?; + let is_open = entry.token.kind.open_delim().is_some(); + if !is_open && entry.token.kind.close_delim().is_some() { + // End of the current nesting level. + return None; + } + let close_idx = if is_open { + let close_idx = self.buf.matches[i] as usize; + debug_assert!(close_idx > i && close_idx < end); + close_idx + } else { + i + }; + if remaining <= 1 { + return Some(if is_open { + let close = &entries[close_idx]; + TokenTree::Delimited( + DelimSpan::from_pair(entry.token.span, close.token.span), + DelimSpacing::new(entry.spacing, close.spacing), + entry.token.kind.open_delim().unwrap(), + TokenStream::from_flat_view(FlatTokenSlice::new( + Arc::clone(&self.buf), + i as u32 + 1, + close_idx as u32, + )), + ) + } else { + TokenTree::Token(entry.token, entry.spacing) + }); + } + remaining -= 1; + i = close_idx + 1; + } + } + + /// The entry index of the whole delimited group whose open-delimiter + /// entry produced `open_token` (the parser's current token), returned as + /// a slice of the buffer plus the entry index of its close delimiter. + /// Panics if the current token is not backed by an open-delimiter entry, + /// e.g. if it was injected via `bump_with`: capturing would silently + /// cover the wrong range. + pub fn current_group_slice(&self, open_token: &Token) -> (FlatTokenSlice, u32) { + let open_idx = self.index.checked_sub(1).expect("no consumed entry to capture"); + let entry = &self.buf.entries[open_idx as usize]; + assert!( + entry.token.kind.open_delim().is_some(), + "current token is not a buffer-backed open delimiter" + ); + // Compare kinds only: `Parser::bump` rewrites a dummy entry span to + // a fallback span for diagnostics, so the parser's current token can + // differ from the buffer entry in span while being the same token. + debug_assert_eq!(entry.token.kind, open_token.kind); + let close_idx = self.buf.matches[open_idx as usize]; + debug_assert!(close_idx > open_idx); + (FlatTokenSlice::new(Arc::clone(&self.buf), open_idx, close_idx + 1), close_idx) + } + + /// The current entry index: the entry the next `inlined_next` call + /// consumes (or starts skipping from). + pub fn position(&self) -> u32 { + self.index + } + + /// The full backing buffer of this cursor, for validation in tests. Not + /// part of the parsing API. + #[doc(hidden)] + pub fn raw_parts(&self) -> (&[FlatEntry], &[u32]) { + (&self.buf.entries, &self.buf.matches) + } + + /// Moves the cursor forward to `index`, skipping everything in between. + pub fn reposition_forward(&mut self, index: u32) { + debug_assert!(index >= self.index && index <= self.end); + self.index = index; + } +} + +/// Whether every open-delimiter entry has a patched match index pointing at +/// a close-delimiter entry after it, and every other entry a zero one. +/// Debug-assertion helper for the buffer producers. +fn flat_buffer_is_well_formed(entries: &[FlatEntry], matches: &[u32]) -> bool { + entries.len() == matches.len() + && entries.iter().enumerate().all(|(i, e)| { + if e.token.kind.open_delim().is_some() { + let m = matches[i] as usize; + m > i && m < entries.len() && entries[m].token.kind.close_delim().is_some() + } else { + matches[i] == 0 + } + }) +} + +/// Rebuilds the token tree for the buffer range `start..end`, which must lie +/// entirely at one nesting level (delimited sequences fully contained). +fn flat_range_to_stream( + entries: &[FlatEntry], + matches: &[u32], + start: usize, + end: usize, +) -> TokenStream { + TokenStream::new(flat_range_to_trees(entries, matches, start, end)) +} + +fn flat_range_to_trees( + entries: &[FlatEntry], + matches: &[u32], + start: usize, + end: usize, +) -> Vec { + let mut trees = Vec::new(); + let mut i = start; + while i < end { + let entry = &entries[i]; + if entry.token.kind.open_delim().is_some() { + trees.push(flat_delimited_at(entries, matches, i)); + let next = matches[i] as usize + 1; + // A close outside `start..end` means the range does not cover + // whole nesting levels; without this check tokens beyond the + // requested range would be silently included. + debug_assert!(next > i && next <= end); + i = next; + } else { + debug_assert!( + entry.token.kind.close_delim().is_none(), + "range starts inside a delimited group" + ); + trees.push(TokenTree::Token(entry.token, entry.spacing)); + i += 1; + } + } + trees +} + +/// Rebuilds the `TokenTree::Delimited` whose open delimiter lives at +/// `open_idx` in the flat token buffer. +fn flat_delimited_at(entries: &[FlatEntry], matches: &[u32], open_idx: usize) -> TokenTree { + let open = &entries[open_idx]; + let close_idx = matches[open_idx] as usize; + debug_assert!(close_idx > open_idx, "unpatched match index for open-delimiter entry"); + let close = &entries[close_idx]; + let delim = open.token.kind.open_delim().unwrap(); + TokenTree::Delimited( + DelimSpan::from_pair(open.token.span, close.token.span), + DelimSpacing::new(open.spacing, close.spacing), + delim, + flat_range_to_stream(entries, matches, open_idx + 1, close_idx), + ) } #[derive(Clone, Debug)] @@ -1080,10 +1935,14 @@ mod size_asserts { // tidy-alphabetical-start static_assert_size!(AttrTokenStream, 8); static_assert_size!(AttrTokenTree, 32); + static_assert_size!(FlatEntry, 32); + static_assert_size!(FlatTokenCursor, 16); + static_assert_size!(FlatTokenSlice, 16); static_assert_size!(LazyAttrTokenStream, 8); - static_assert_size!(LazyAttrTokenStreamInner, 88); + static_assert_size!(LazyAttrTokenStreamInner, 64); static_assert_size!(Option, 8); // must be small, used in many AST nodes static_assert_size!(TokenStream, 8); + static_assert_size!(TokenStreamInner, 24); // niches on the `Flat` box: eager streams pay no view overhead static_assert_size!(TokenTree, 32); // tidy-alphabetical-end } diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index f15b005960c9f..28231dc3b8a5e 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast::attr::MarkedAttrs; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{FlatTokenCursor, TokenStream}; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind, Safety}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; @@ -278,8 +278,23 @@ impl<'cx> MacroExpanderResult<'cx> { ) -> Self { // Emit SEMICOLON_IN_EXPRESSIONS_FROM_MACROS here, rather than the NON_LOCAL version. let is_local = true; - let parser = - ParserAnyMacro::from_tts(cx, tts, site_span, arm_span, is_local, macro_ident, &[], &[]); + + // Parse an existing view in place; only tree-backed streams need the + // flattening pass. + let cursor = match tts.flat_view() { + Some(view) => FlatTokenCursor::from_view(view), + None => FlatTokenCursor::new(tts), + }; + let parser = ParserAnyMacro::from_flat( + cx, + cursor, + site_span, + arm_span, + is_local, + macro_ident, + &[], + &[], + ); ExpandResult::Ready(Box::new(parser)) } } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index f6d12fa19c141..9361c85ab3e01 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -7,7 +7,7 @@ use ast::token::IdentIsRaw; use rustc_ast::token::NtPatKind::*; use rustc_ast::token::TokenKind::*; use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind}; -use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; +use rustc_ast::tokenstream::{self, DelimSpan, FlatTokenCursor, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId, Safety}; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; @@ -120,10 +120,10 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { fragment } - #[instrument(skip(cx, tts, bindings, matched_rule_bindings))] - pub(crate) fn from_tts<'cx>( + #[instrument(skip(cx, flat, bindings, matched_rule_bindings))] + pub(crate) fn from_flat<'cx>( cx: &'cx mut ExtCtxt<'a>, - tts: TokenStream, + flat: FlatTokenCursor, site_span: Span, arm_span: Span, is_local: bool, @@ -133,7 +133,7 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { matched_rule_bindings: &'b [MatcherLoc], ) -> Self { Self { - parser: Parser::new(&cx.sess.psess, tts, None), + parser: Parser::new_from_flat(&cx.sess.psess, flat, None), // Pass along the original expansion site and the name of the macro // so we can print a useful error message if the parse of the expanded @@ -256,7 +256,8 @@ impl MacroRulesMacroExpander { let id = cx.current_expansion.id; let tts = transcribe(psess, &named_matches, rhs, *rhs_span, self.transparency, id) - .map_err(|e| e.emit())?; + .map_err(|e| e.emit())? + .to_token_stream(); if cx.trace_macros() { let msg = format!("to `{}`", pprust::tts_to_string(&tts)); @@ -462,8 +463,8 @@ fn expand_macro<'cx, 'a: 'cx>( // rhs has holes ( `$id` and `$(...)` that need filled) let id = cx.current_expansion.id; - let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) { - Ok(tts) => tts, + let flat = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) { + Ok(flat) => flat, Err(err) => { let guar = err.emit(); return DummyResult::any(arm_span, guar); @@ -471,7 +472,7 @@ fn expand_macro<'cx, 'a: 'cx>( }; if cx.trace_macros() { - let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + let msg = format!("to `{}`", pprust::tts_to_string(&flat.to_token_stream())); trace_macros_note(&mut cx.expansions, sp, msg); } @@ -481,7 +482,7 @@ fn expand_macro<'cx, 'a: 'cx>( } // Let the context choose how to interpret the result. Weird, but useful for X-macros. - Box::new(ParserAnyMacro::from_tts(cx, tts, sp, arm_span, is_local, name, rules, lhs)) + Box::new(ParserAnyMacro::from_flat(cx, flat, sp, arm_span, is_local, name, rules, lhs)) } Err(CanRetry::No(guar)) => { debug!("Will not retry matching as an error was emitted already"); @@ -559,7 +560,8 @@ fn expand_macro_attr( let id = cx.current_expansion.id; let tts = transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) - .map_err(|e| e.emit())?; + .map_err(|e| e.emit())? + .to_token_stream(); if cx.trace_macros() { let msg = format!("to `{}`", pprust::tts_to_string(&tts)); @@ -1865,6 +1867,18 @@ pub(super) fn parser_from_cx( mut tts: TokenStream, recovery: Recovery, ) -> Parser<'_> { + // Macro-invocation arguments usually arrive as a lazy view of the flat + // token buffer; parse straight from it, unless doc comments require the + // desugaring pre-pass (rare). The scan is O(arguments) per invocation, + // but so was the tree walk `desugar_doc_comments` did here before; the + // flat scan replaces it, not adds to it. + if let Some(view) = tts.flat_view() + && !view.entries().iter().any(|e| matches!(e.token().kind, token::DocComment(..))) + { + let cursor = FlatTokenCursor::from_view(view); + return Parser::new_from_flat(psess, cursor, rustc_parse::MACRO_ARGUMENTS) + .recovery(recovery); + } tts.desugar_doc_comments(); Parser::new(psess, tts, rustc_parse::MACRO_ARGUMENTS).recovery(recovery) } diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index eabec05cd66c6..2f13f8fe450b2 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -1,9 +1,9 @@ -use std::mem; - use rustc_ast::token::{ self, Delimiter, IdentIsRaw, InvisibleOrigin, Lit, LitKind, MetaVarKind, Token, TokenKind, }; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenstream::{ + DelimSpacing, DelimSpan, FlatSink, FlatTokenCursor, FlatTt, Spacing, TokenStream, TokenTree, +}; use rustc_ast::{ExprKind, StmtKind, TyKind, UnOp}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Diag, DiagCtxtHandle, PResult, listify, pluralize}; @@ -52,23 +52,11 @@ struct TranscrCtx<'psess, 'itp> { /// being the most deeply nested sequence. This is used as a stack. repeats: Vec<(usize, usize)>, - /// The resulting token stream from the `TokenTree` we just finished processing. - /// - /// At the end, this will contain the full result of transcription, but at arbitrary points - /// during `transcribe`, `result` will contain subsets of the final result. - /// - /// Specifically, as we descend into each TokenTree, we will push the existing results onto the - /// `result_stack` and clear `results`. We will then produce the results of transcribing the - /// TokenTree into `results`. Then, as we unwind back out of the `TokenTree`, we will pop the - /// `result_stack` and append `results` too it to produce the new `results` up to that point. - /// - /// Thus, if we try to pop the `result_stack` and it is empty, we have reached the top-level - /// again, and we are done transcribing. - result: Vec, - - /// The in-progress `result` lives at the top of this stack. Each entered `TokenTree` adds a - /// new entry. - result_stack: Vec>, + /// The transcription result, built directly in the parser's flat form. + /// Entering a nested `Delimited` emits an open-delimiter entry and + /// leaving it emits the close entry, so no result stack is needed — the + /// buffer is append-only. + sink: FlatSink, } impl<'psess> TranscrCtx<'psess, '_> { @@ -160,7 +148,7 @@ impl<'a> Iterator for Frame<'a> { /// /// `interp` would contain `$id => bar` and `src` would contain `println!("{}", stringify!($id));`. /// -/// `transcribe` would return a `TokenStream` containing `println!("{}", stringify!(bar));`. +/// `transcribe` would return a token buffer containing `println!("{}", stringify!(bar));`. /// /// Along the way, we do some additional error checking. pub(super) fn transcribe<'a>( @@ -170,10 +158,10 @@ pub(super) fn transcribe<'a>( src_span: DelimSpan, transparency: Transparency, expand_id: LocalExpnId, -) -> PResult<'a, TokenStream> { +) -> PResult<'a, FlatTokenCursor> { // Nothing for us to transcribe... if src.tts.is_empty() { - return Ok(TokenStream::default()); + return Ok(FlatSink::new().finish()); } let mut tscx = TranscrCtx { @@ -186,8 +174,10 @@ pub(super) fn transcribe<'a>( src_span, DelimSpacing::new(Spacing::Alone, Spacing::Alone) )], - result: Vec::new(), - result_stack: Vec::new(), + // The output typically contains at least one entry per template + // token tree, so the template length is a cheap capacity estimate + // that avoids the initial growth ladder of the result buffer. + sink: FlatSink::with_capacity(src.tts.len()), }; loop { @@ -205,7 +195,7 @@ pub(super) fn transcribe<'a>( if repeat_idx < repeat_len { frame.idx = 0; if let Some(sep) = sep { - tscx.result.push(TokenTree::Token(*sep, Spacing::Alone)); + tscx.sink.push_token(*sep, Spacing::Alone); } continue; } @@ -221,24 +211,24 @@ pub(super) fn transcribe<'a>( } // We are done processing a Delimited. If this is the top-level delimited, we are - // done. Otherwise, we unwind the result_stack to append what we have produced to - // any previous results. + // done (its delimiters are not part of the result). Otherwise, emit the close + // delimiter entry. FrameKind::Delimited { delim, span, mut spacing, .. } => { // Hack to force-insert a space after `]` in certain case. // See discussion of the `hex-literal` crate in #114571. if delim == Delimiter::Bracket { spacing.close = Spacing::Alone; } - if tscx.result_stack.is_empty() { + if tscx.stack.is_empty() { // No results left to compute! We are back at the top-level. - return Ok(TokenStream::new(tscx.result)); + return Ok(tscx.sink.finish()); } - // Step back into the parent Delimited. - let tree = - TokenTree::Delimited(span, spacing, delim, TokenStream::new(tscx.result)); - tscx.result = tscx.result_stack.pop().unwrap(); - tscx.result.push(tree); + // The delimiter spans were already marked when the frame was entered. + tscx.sink.close_delim( + Token::new(delim.as_close_token_kind(), span.close), + spacing.close, + ); } } continue; @@ -270,8 +260,11 @@ pub(super) fn transcribe<'a>( &mbe::TokenTree::Delimited(mut span, ref spacing, ref delimited) => { tscx.marker.mark_span(&mut span.open); tscx.marker.mark_span(&mut span.close); + tscx.sink.open_delim( + Token::new(delimited.delim.as_open_token_kind(), span.open), + spacing.open, + ); tscx.stack.push(Frame::new_delimited(delimited, span, *spacing)); - tscx.result_stack.push(mem::take(&mut tscx.result)); } // Nothing much to do here. Just push the token to the result, being careful to @@ -281,8 +274,7 @@ pub(super) fn transcribe<'a>( if let token::NtIdent(ident, _) | token::NtLifetime(ident, _) = &mut token.kind { tscx.marker.mark_span(&mut ident.span); } - let tt = TokenTree::Token(token, Spacing::Alone); - tscx.result.push(tt); + tscx.sink.push_token(token, Spacing::Alone); } // There should be no meta-var declarations in the invocation of a macro. @@ -434,8 +426,8 @@ fn transcribe_metavar<'tx>( // with modified syntax context. (I believe this supports nested macros). tscx.marker.mark_span(&mut sp); tscx.marker.mark_span(&mut original_ident.span); - tscx.result.push(TokenTree::token_joint_hidden(token::Dollar, sp)); - tscx.result.push(TokenTree::Token(Token::from_ast_ident(original_ident), Spacing::Alone)); + tscx.sink.push_token(Token::new(token::Dollar, sp), Spacing::JointHidden); + tscx.sink.push_token(Token::from_ast_ident(original_ident), Spacing::Alone); return Ok(()); }; @@ -452,61 +444,40 @@ fn transcribe_pnr<'tx>( mut sp: Span, pnr: &ParseNtResult, ) -> PResult<'tx, ()> { - // We wrap the tokens in invisible delimiters, unless they are already wrapped - // in invisible delimiters with the same `MetaVarKind`. Because some proc - // macros can't handle multiple layers of invisible delimiters of the same - // `MetaVarKind`. This loses some span info, though it hopefully won't matter. - let mut mk_delimited = |mk_span, mv_kind, mut stream: TokenStream| { - if stream.len() == 1 { - let tree = stream.iter().next().unwrap(); - if let TokenTree::Delimited(_, _, delim, inner) = tree - && let Delimiter::Invisible(InvisibleOrigin::MetaVar(mvk)) = delim - && mv_kind == *mvk - { - stream = inner.clone(); - } - } - - // Emit as a token stream within `Delimiter::Invisible` to maintain - // parsing priorities. - tscx.marker.mark_span(&mut sp); - with_metavar_spans(|mspans| mspans.insert(mk_span, sp)); - // Both the open delim and close delim get the same span, which covers the - // `$foo` in the decl macro RHS. - TokenTree::Delimited( - DelimSpan::from_single(sp), - DelimSpacing::new(Spacing::Alone, Spacing::Alone), - Delimiter::Invisible(InvisibleOrigin::MetaVar(mv_kind)), - stream, - ) - }; - - let tt = match pnr { - ParseNtResult::Tt(tt) => { + match pnr { + ParseNtResult::Tt(ftt) => { // `tt`s are emitted into the output stream directly as "raw tokens", // without wrapping them into groups. Other variables are emitted into // the output stream as groups with `Delimiter::Invisible` to maintain // parsing priorities. - maybe_use_metavar_location(tscx.psess, &tscx.stack, sp, tt, &mut tscx.marker) + transcribe_flat_tt(tscx, sp, ftt); } ParseNtResult::Ident(ident, is_raw) => { tscx.marker.mark_span(&mut sp); with_metavar_spans(|mspans| mspans.insert(ident.span, sp)); let kind = token::NtIdent(*ident, *is_raw); - TokenTree::token_alone(kind, sp) + tscx.sink.push_token(Token::new(kind, sp), Spacing::Alone); } ParseNtResult::Lifetime(ident, is_raw) => { tscx.marker.mark_span(&mut sp); with_metavar_spans(|mspans| mspans.insert(ident.span, sp)); let kind = token::NtLifetime(*ident, *is_raw); - TokenTree::token_alone(kind, sp) - } - ParseNtResult::Item(item) => { - mk_delimited(item.span, MetaVarKind::Item, TokenStream::from_ast(item)) - } - ParseNtResult::Block(block) => { - mk_delimited(block.node.span, MetaVarKind::Block, TokenStream::from_ast(block)) + tscx.sink.push_token(Token::new(kind, sp), Spacing::Alone); } + ParseNtResult::Item(item) => emit_delimited_fragment( + tscx, + sp, + item.span, + MetaVarKind::Item, + TokenStream::from_ast(item), + ), + ParseNtResult::Block(block) => emit_delimited_fragment( + tscx, + sp, + block.node.span, + MetaVarKind::Block, + TokenStream::from_ast(block), + ), ParseNtResult::Stmt(stmt) => { let stream = if let StmtKind::Empty = stmt.kind { // FIXME: Properly collect tokens for empty statements. @@ -514,11 +485,15 @@ fn transcribe_pnr<'tx>( } else { TokenStream::from_ast(stmt) }; - mk_delimited(stmt.span, MetaVarKind::Stmt, stream) - } - ParseNtResult::Pat(pat, pat_kind) => { - mk_delimited(pat.node.span, MetaVarKind::Pat(*pat_kind), TokenStream::from_ast(pat)) + emit_delimited_fragment(tscx, sp, stmt.span, MetaVarKind::Stmt, stream) } + ParseNtResult::Pat(pat, pat_kind) => emit_delimited_fragment( + tscx, + sp, + pat.node.span, + MetaVarKind::Pat(*pat_kind), + TokenStream::from_ast(pat), + ), ParseNtResult::Expr(expr, kind) => { let (can_begin_literal_maybe_minus, can_begin_string_literal) = match &expr.kind { ExprKind::Lit(_) => (true, true), @@ -527,7 +502,9 @@ fn transcribe_pnr<'tx>( } _ => (false, false), }; - mk_delimited( + emit_delimited_fragment( + tscx, + sp, expr.span, MetaVarKind::Expr { kind: *kind, @@ -537,27 +514,47 @@ fn transcribe_pnr<'tx>( TokenStream::from_ast(expr), ) } - ParseNtResult::Literal(lit) => { - mk_delimited(lit.span, MetaVarKind::Literal, TokenStream::from_ast(lit)) - } + ParseNtResult::Literal(lit) => emit_delimited_fragment( + tscx, + sp, + lit.span, + MetaVarKind::Literal, + TokenStream::from_ast(lit), + ), ParseNtResult::Ty(ty) => { let is_path = matches!(&ty.node.kind, TyKind::Path(None, _path)); - mk_delimited(ty.node.span, MetaVarKind::Ty { is_path }, TokenStream::from_ast(ty)) + emit_delimited_fragment( + tscx, + sp, + ty.node.span, + MetaVarKind::Ty { is_path }, + TokenStream::from_ast(ty), + ) } ParseNtResult::Meta(attr_item) => { let has_meta_form = attr_item.node.meta_kind().is_some(); - mk_delimited( + emit_delimited_fragment( + tscx, + sp, attr_item.node.span, MetaVarKind::Meta { has_meta_form }, TokenStream::from_ast(attr_item), ) } - ParseNtResult::Path(path) => { - mk_delimited(path.node.span, MetaVarKind::Path, TokenStream::from_ast(path)) - } - ParseNtResult::Vis(vis) => { - mk_delimited(vis.node.span, MetaVarKind::Vis, TokenStream::from_ast(vis)) - } + ParseNtResult::Path(path) => emit_delimited_fragment( + tscx, + sp, + path.node.span, + MetaVarKind::Path, + TokenStream::from_ast(path), + ), + ParseNtResult::Vis(vis) => emit_delimited_fragment( + tscx, + sp, + vis.node.span, + MetaVarKind::Vis, + TokenStream::from_ast(vis), + ), ParseNtResult::Guard(guard) => { // FIXME(macro_guard_matcher): // Perhaps it would be better to treat the leading `if` as part of `ast::Guard` during parsing? @@ -572,11 +569,10 @@ fn transcribe_pnr<'tx>( .chain(TokenStream::from_ast(&guard.cond).iter().cloned()) .collect(); - mk_delimited(guard.span_with_leading_if, MetaVarKind::Guard, ts) + emit_delimited_fragment(tscx, sp, guard.span_with_leading_if, MetaVarKind::Guard, ts) } }; - tscx.result.push(tt); Ok(()) } @@ -587,12 +583,12 @@ fn transcribe_metavar_expr<'tx>( expr: &MetaVarExpr, ) -> PResult<'tx, ()> { let dcx = tscx.psess.dcx(); - let tt = match *expr { + let token = match *expr { MetaVarExpr::Concat(ref elements) => metavar_expr_concat(tscx, dspan, elements)?, MetaVarExpr::Count(original_ident, depth) => { let matched = matched_from_ident(dcx, original_ident, tscx.interp)?; let count = count_repetitions(dcx, depth, matched, &tscx.repeats, &dspan)?; - TokenTree::token_alone( + Token::new( TokenKind::lit(token::Integer, sym::integer(count), None), tscx.visited_dspan(dspan), ) @@ -603,7 +599,7 @@ fn transcribe_metavar_expr<'tx>( return Ok(()); } MetaVarExpr::Index(depth) => match tscx.repeats.iter().nth_back(depth) { - Some((index, _)) => TokenTree::token_alone( + Some((index, _)) => Token::new( TokenKind::lit(token::Integer, sym::integer(*index), None), tscx.visited_dspan(dspan), ), @@ -612,7 +608,7 @@ fn transcribe_metavar_expr<'tx>( } }, MetaVarExpr::Len(depth) => match tscx.repeats.iter().nth_back(depth) { - Some((_, length)) => TokenTree::token_alone( + Some((_, length)) => Token::new( TokenKind::lit(token::Integer, sym::integer(*length), None), tscx.visited_dspan(dspan), ), @@ -621,7 +617,7 @@ fn transcribe_metavar_expr<'tx>( } }, }; - tscx.result.push(tt); + tscx.sink.push_token(token, Spacing::Alone); Ok(()) } @@ -630,7 +626,7 @@ fn metavar_expr_concat<'tx>( tscx: &mut TranscrCtx<'tx, '_>, dspan: DelimSpan, elements: &[MetaVarExprConcatElem], -) -> PResult<'tx, TokenTree> { +) -> PResult<'tx, Token> { let dcx = tscx.psess.dcx(); let mut concatenated = String::new(); for element in elements { @@ -670,10 +666,7 @@ fn metavar_expr_concat<'tx>( // The current implementation marks the span as coming from the macro regardless of // contexts of the concatenated identifiers but this behavior may change in the // future. - Ok(TokenTree::Token( - Token::from_ast_ident(Ident::new(symbol, concatenated_span)), - Spacing::Alone, - )) + Ok(Token::from_ast_ident(Ident::new(symbol, concatenated_span))) } /// Store the metavariable span for this original span into a side table. @@ -706,15 +699,10 @@ fn metavar_expr_concat<'tx>( /// These are typically used for passing larger amounts of code, and tokens in that code usually /// combine with each other and not with tokens outside of the sequence. /// - The metavariable span comes from a different crate, then we prefer the more local span. -fn maybe_use_metavar_location( - psess: &ParseSess, - stack: &[Frame<'_>], - mut metavar_span: Span, - orig_tt: &TokenTree, - marker: &mut Marker, -) -> TokenTree { +fn transcribe_flat_tt(tscx: &mut TranscrCtx<'_, '_>, metavar_span: Span, ftt: &FlatTt) { + let mut metavar_span = metavar_span; let undelimited_seq = matches!( - stack.last(), + tscx.stack.last(), Some(Frame { tts: [_], kind: FrameKind::Sequence { @@ -727,44 +715,99 @@ fn maybe_use_metavar_location( ); if undelimited_seq { // Do not record metavar spans for tokens from undelimited sequences, for perf reasons. - return orig_tt.clone(); + splice_flat_tt(&mut tscx.sink, ftt); + return; } - marker.mark_span(&mut metavar_span); - let no_collision = match orig_tt { - TokenTree::Token(token, ..) => { + tscx.marker.mark_span(&mut metavar_span); + let no_collision = match ftt { + FlatTt::Token(token, ..) => { with_metavar_spans(|mspans| mspans.insert(token.span, metavar_span)) } - TokenTree::Delimited(dspan, ..) => with_metavar_spans(|mspans| { - mspans.insert(dspan.open, metavar_span) - && mspans.insert(dspan.close, metavar_span) - && mspans.insert(dspan.entire(), metavar_span) - }), + FlatTt::Slice(slice) => { + let entries = slice.entries(); + let (open, close) = (entries.first().unwrap(), entries.last().unwrap()); + let dspan = DelimSpan::from_pair(open.token().span, close.token().span); + with_metavar_spans(|mspans| { + mspans.insert(dspan.open, metavar_span) + && mspans.insert(dspan.close, metavar_span) + && mspans.insert(dspan.entire(), metavar_span) + }) + } }; - if no_collision || psess.source_map().is_imported(metavar_span) { - return orig_tt.clone(); + if no_collision || tscx.psess.source_map().is_imported(metavar_span) { + splice_flat_tt(&mut tscx.sink, ftt); + return; } // Setting metavar spans for the heuristic spans gives better opportunities for combining them // with neighboring spans even despite their different syntactic contexts. - match orig_tt { - TokenTree::Token(Token { kind, span }, spacing) => { + match ftt { + FlatTt::Token(Token { kind, span }, spacing) => { let span = metavar_span.with_ctxt(span.ctxt()); with_metavar_spans(|mspans| mspans.insert(span, metavar_span)); - TokenTree::Token(Token { kind: *kind, span }, *spacing) + tscx.sink.push_token(Token { kind: *kind, span }, *spacing); } - TokenTree::Delimited(dspan, dspacing, delimiter, tts) => { - let open = metavar_span.with_ctxt(dspan.open.ctxt()); - let close = metavar_span.with_ctxt(dspan.close.ctxt()); + FlatTt::Slice(slice) => { + let entries = slice.entries(); + let (open_span, close_span) = + (entries.first().unwrap().token().span, entries.last().unwrap().token().span); + let open = metavar_span.with_ctxt(open_span.ctxt()); + let close = metavar_span.with_ctxt(close_span.ctxt()); with_metavar_spans(|mspans| { mspans.insert(open, metavar_span) && mspans.insert(close, metavar_span) }); - let dspan = DelimSpan::from_pair(open, close); - TokenTree::Delimited(dspan, *dspacing, *delimiter, tts.clone()) + // Splice the group and rewrite the delimiter entries' spans. + let range = tscx.sink.splice_slice(slice); + tscx.sink.set_boundary_spans(range, open, close); + } + } +} + +/// Appends a captured `tt` fragment verbatim. +fn splice_flat_tt(sink: &mut FlatSink, ftt: &FlatTt) { + match ftt { + FlatTt::Slice(slice) => { + sink.splice_slice(slice); } + FlatTt::Token(token, spacing) => sink.push_token(*token, *spacing), } } +/// Emits a non-`tt` metavariable fragment: its tokens wrapped in invisible +/// delimiters (unless already wrapped in invisible delimiters with the same +/// `MetaVarKind`, because some proc macros can't handle multiple layers of +/// invisible delimiters of the same `MetaVarKind`; this loses some span +/// info, though it hopefully won't matter). +fn emit_delimited_fragment( + tscx: &mut TranscrCtx<'_, '_>, + mut sp: Span, + mk_span: Span, + mv_kind: MetaVarKind, + mut stream: TokenStream, +) { + if stream.len() == 1 { + let tree = stream.iter().next().unwrap(); + if let TokenTree::Delimited(_, _, delim, inner) = tree + && let Delimiter::Invisible(InvisibleOrigin::MetaVar(mvk)) = delim + && mv_kind == *mvk + { + stream = inner.clone(); + } + } + + // Emit as tokens within `Delimiter::Invisible` to maintain parsing + // priorities. + tscx.marker.mark_span(&mut sp); + with_metavar_spans(|mspans| mspans.insert(mk_span, sp)); + // Both the open delim and close delim get the same span, which covers the + // `$foo` in the decl macro RHS. + let delim = Delimiter::Invisible(InvisibleOrigin::MetaVar(mv_kind)); + tscx.sink.open_delim(Token::new(delim.as_open_token_kind(), sp), Spacing::Alone); + tscx.sink.splice_stream(&stream); + tscx.sink.close_delim(Token::new(delim.as_close_token_kind(), sp), Spacing::Alone); +} + /// Lookup the meta-var named `ident` and return the matched token tree from the invocation using /// the set of matches `interpolations`. /// @@ -1002,7 +1045,7 @@ fn extract_symbol_from_pnr<'a>( Ok(nt_ident.name) } } - ParseNtResult::Tt(TokenTree::Token( + ParseNtResult::Tt(FlatTt::Token( Token { kind: TokenKind::Ident(symbol, is_raw), .. }, _, )) => { @@ -1012,7 +1055,7 @@ fn extract_symbol_from_pnr<'a>( Ok(*symbol) } } - ParseNtResult::Tt(TokenTree::Token( + ParseNtResult::Tt(FlatTt::Token( Token { kind: TokenKind::Literal(Lit { kind: LitKind::Str, symbol, suffix: None }), .. diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2737ec849ba24..091e623251613 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1783,32 +1783,55 @@ declare_lint_pass!( struct UnderMacro(bool); impl KeywordIdents { + /// Checks one macro token, tracking whether the preceding token was `$` + /// (so `$async` etc. are allowed) and reporting only non-raw idents. + /// Shared between the flat-buffer scan and the token-tree walk in + /// `check_tokens`, which must lint identically. + fn check_macro_token( + &mut self, + cx: &EarlyContext<'_>, + token: &ast::token::Token, + prev_dollar: &mut bool, + ) { + if let Some((ident, token::IdentIsRaw::No)) = token.ident() { + if !*prev_dollar { + self.check_ident_token(cx, UnderMacro(true), ident, ""); + } + } else if let Some((ident, token::IdentIsRaw::No)) = token.lifetime() { + self.check_ident_token(cx, UnderMacro(true), ident.without_first_quote(), "'"); + } else if token.kind == TokenKind::Dollar { + *prev_dollar = true; + return; + } + *prev_dollar = false; + } + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: &TokenStream) { - // Check if the preceding token is `$`, because we want to allow `$async`, etc. + // A stream that is a lazy view of a flat token buffer can be checked + // in place: every token (including nested group contents) is an + // entry, and delimiter entries reset the `$` state exactly like the + // `Delimited` arm of the tree walk below. This keeps the + // pre-expansion lint from materializing the tree of every macro + // invocation's arguments. + if let Some(view) = tokens.flat_view() { + let mut prev_dollar = false; + for entry in view.entries() { + self.check_macro_token(cx, entry.token(), &mut prev_dollar); + } + return; + } + let mut prev_dollar = false; for tt in tokens.iter() { match tt { - // Only report non-raw idents. TokenTree::Token(token, _) => { - if let Some((ident, token::IdentIsRaw::No)) = token.ident() { - if !prev_dollar { - self.check_ident_token(cx, UnderMacro(true), ident, ""); - } - } else if let Some((ident, token::IdentIsRaw::No)) = token.lifetime() { - self.check_ident_token( - cx, - UnderMacro(true), - ident.without_first_quote(), - "'", - ); - } else if token.kind == TokenKind::Dollar { - prev_dollar = true; - continue; - } + self.check_macro_token(cx, token, &mut prev_dollar); + } + TokenTree::Delimited(.., tts) => { + self.check_tokens(cx, tts); + prev_dollar = false; } - TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), } - prev_dollar = false; } } diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 4f7c76e7df816..7c9becbd294e3 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -1,7 +1,7 @@ use diagnostics::make_errors_for_mismatched_closing_delims; use rustc_ast::ast::{self, AttrStyle}; use rustc_ast::token::{self, CommentKind, Delimiter, IdentIsRaw, Token, TokenKind}; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{FlatSink, FlatTokenCursor}; use rustc_ast::util::unicode::{TEXT_FLOW_CONTROL_CHARS, contains_text_flow_control_chars}; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, StashKey}; @@ -68,7 +68,7 @@ pub(crate) fn lex_token_trees<'psess, 'src>( mut start_pos: BytePos, override_span: Option, strip_tokens: StripTokens, -) -> Result>> { +) -> Result>> { match strip_tokens { StripTokens::Shebang | StripTokens::ShebangAndFrontmatter => { if let Some(shebang_len) = rustc_lexer::strip_shebang(src) { @@ -97,15 +97,27 @@ pub(crate) fn lex_token_trees<'psess, 'src>( token: Token::dummy(), diag_info: TokenTreeDiagInfo::default(), }; - let res = lexer.lex_token_trees(/* is_delimited */ false); + // Lexing produces the parser's flat token buffer directly; a token *tree* + // is only rebuilt from it for the few callers that need one. + // + // Pre-size the buffer at one token per 6 source bytes. Measured over + // rust-lang/rust itself (non-trivia lexer tokens, files >= 256 bytes), + // byte-weighted density is ~7.0 bytes/token for compiler/ and ~5.9 for + // library/, with per-file quartiles roughly 5.1/6.1/7.4 and pathological + // token-stress files down at ~1.7. Estimating on the dense side is + // deliberate: undershoot costs one partial regrowth copy, while + // overshoot within the slack threshold of `finish` is only transient + // waste (beyond it, `finish` shrinks, which copies the full buffer). + let mut sink = FlatSink::with_capacity(src.len() / 6 + 16); + let res = lexer.lex_token_trees(/* is_delimited */ false, &mut sink); let mut unmatched_closing_delims: Vec<_> = make_errors_for_mismatched_closing_delims(&lexer.diag_info.unmatched_delims, psess); match res { - Ok((_open_spacing, stream)) => { + Ok(_open_spacing) => { if unmatched_closing_delims.is_empty() { - Ok(stream) + Ok(sink.finish()) } else { // Return error if there are unmatched delimiters or unclosed delimiters. Err(unmatched_closing_delims) diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 757cd755bf65f..3f6510cf8f0d0 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -1,5 +1,5 @@ use rustc_ast::token::{self, Delimiter, Token}; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenstream::{FlatSink, Spacing}; use rustc_ast_pretty::pprust::token_to_string; use rustc_errors::Diag; @@ -9,44 +9,41 @@ use super::diagnostics::{ use super::{Lexer, UnmatchedDelim}; impl<'psess, 'src> Lexer<'psess, 'src> { - // Lex into a token stream. The `Spacing` in the result is that of the - // opening delimiter. + // Lex into a flat token buffer through `sink`. The returned `Spacing` is + // that of the opening delimiter. Delimited sequences are emitted as an + // open-delimiter entry, the contents, and a close-delimiter entry; the + // sink maintains the depth and match-table invariants, so this produces + // exactly the buffer that `FlatTokenCursor::new` would build from the + // token *tree*, without materializing the tree. pub(super) fn lex_token_trees( &mut self, is_delimited: bool, - ) -> Result<(Spacing, TokenStream), Diag<'psess>> { + sink: &mut FlatSink, + ) -> Result> { // Move past the opening delimiter. let open_spacing = self.bump_minimal(); - let mut buf = Vec::new(); loop { if let Some(delim) = self.token.kind.open_delim() { // Invisible delimiters cannot occur here because `TokenTreesReader` parses // code directly from strings, with no macro expansion involved. debug_assert!(!matches!(delim, Delimiter::Invisible(_))); - buf.push(match self.lex_token_tree_open_delim(delim) { - Ok(val) => val, - Err(errs) => return Err(errs), - }) + self.lex_token_tree_open_delim(delim, sink)? } else if let Some(delim) = self.token.kind.close_delim() { // Invisible delimiters cannot occur here because `TokenTreesReader` parses // code directly from strings, with no macro expansion involved. debug_assert!(!matches!(delim, Delimiter::Invisible(_))); return if is_delimited { - Ok((open_spacing, TokenStream::new(buf))) + Ok(open_spacing) } else { Err(self.close_delim_err(delim)) }; } else if self.token.kind == token::Eof { - return if is_delimited { - Err(self.eof_err()) - } else { - Ok((open_spacing, TokenStream::new(buf))) - }; + return if is_delimited { Err(self.eof_err()) } else { Ok(open_spacing) }; } else { // Get the next normal token. let (this_tok, this_spacing) = self.bump(); - buf.push(TokenTree::Token(this_tok, this_spacing)); + sink.push_token(this_tok, this_spacing); } } } @@ -54,19 +51,28 @@ impl<'psess, 'src> Lexer<'psess, 'src> { fn lex_token_tree_open_delim( &mut self, open_delim: Delimiter, - ) -> Result> { + sink: &mut FlatSink, + ) -> Result<(), Diag<'psess>> { // The span for beginning of the delimited section. let pre_span = self.token.span; self.diag_info.open_delimiters.push((open_delim, self.token.span)); + // Emit the open-delimiter entry. Its spacing is produced by the + // recursive call below (which bumps past the delimiter), so it is + // patched in afterwards. + let open_idx = sink.open_delim(self.token, Spacing::Alone); + // Lex the token trees within the delimiters. // We stop at any delimiter so we can try to recover if the user // uses an incorrect delimiter. - let (open_spacing, tts) = self.lex_token_trees(/* is_delimited */ true)?; + let open_spacing = self.lex_token_trees(/* is_delimited */ true, sink)?; + sink.patch_open_spacing(open_idx, open_spacing); - // Expand to cover the entire delimited token tree. - let delim_span = DelimSpan::from_pair(pre_span, self.token.span); + // The close-delimiter entry gets this span even in recovery cases, + // mirroring `DelimSpan::from_pair(pre_span, self.token.span)` in the + // tree-building lexer. + let close_span = self.token.span; let sm = self.psess.source_map(); let close_spacing = if let Some(close_delim) = self.token.kind.close_delim() { @@ -75,7 +81,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.diag_info.open_delimiters.pop().unwrap(); let close_delimiter_span = self.token.span; - if tts.is_empty() && close_delim == Delimiter::Brace { + if sink.len() == open_idx + 1 && close_delim == Delimiter::Brace { let empty_block_span = pre_span.to(close_delimiter_span); if !sm.is_multiline(empty_block_span) { // Only track if the block is in the form of `{}`, otherwise it is @@ -148,9 +154,9 @@ impl<'psess, 'src> Lexer<'psess, 'src> { Spacing::Alone }; - let spacing = DelimSpacing::new(open_spacing, close_spacing); + sink.close_delim(Token::new(open_delim.as_close_token_kind(), close_span), close_spacing); - Ok(TokenTree::Delimited(delim_span, spacing, open_delim, tts)) + Ok(()) } // Move on to the next token, returning the current token and its spacing. diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 539c15f18a9a4..64d721d36241d 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -15,7 +15,9 @@ use std::sync::Arc; use rustc_ast as ast; use rustc_ast::token; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenstream::{ + DelimSpacing, DelimSpan, FlatTokenCursor, Spacing, TokenStream, TokenTree, +}; use rustc_ast_pretty::pprust; use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize}; pub use rustc_lexer::UNICODE_VERSION; @@ -229,8 +231,8 @@ fn new_parser_from_source_file( strip_tokens: StripTokens, ) -> Result, Vec>> { let end_pos = source_file.end_position(); - let stream = source_file_to_stream(psess, source_file, None, strip_tokens)?; - let mut parser = Parser::new(psess, stream, None); + let cursor = source_file_to_flat(psess, source_file, None, strip_tokens)?; + let mut parser = Parser::new_from_flat(psess, cursor, None); if parser.token == token::Eof { parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt(), None); } @@ -263,6 +265,20 @@ fn source_file_to_stream<'psess>( override_span: Option, strip_tokens: StripTokens, ) -> Result>> { + // The lexer produces the parser's flat token buffer; rebuild the token + // tree for the callers (proc-macro `from_str`, cmdline attributes, fake + // token streams for diagnostics) that need one. + Ok(source_file_to_flat(psess, source_file, override_span, strip_tokens)?.to_token_stream()) +} + +/// Given a source file, lexes it directly into the parser's flat token +/// buffer, never materializing a token tree. +fn source_file_to_flat<'psess>( + psess: &'psess ParseSess, + source_file: Arc, + override_span: Option, + strip_tokens: StripTokens, +) -> Result>> { let src = source_file.src.as_ref().unwrap_or_else(|| { psess.dcx().bug(format!( "cannot lex `source_file` without source: {}", @@ -350,7 +366,7 @@ fn lex_token_trees_for_span( ) -> Option> { let src = psess.source_map().span_to_snippet(span).ok()?; let stream = match lexer::lex_token_trees(psess, &src, span.lo(), None, StripTokens::Nothing) { - Ok(stream) => stream, + Ok(cursor) => cursor.to_token_stream(), Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); return None; diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index fe34d9951dc5f..360f47ef3e24f 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -3,7 +3,7 @@ use std::mem; use rustc_ast::token::Token; use rustc_ast::tokenstream::{ - AttrsTarget, LazyAttrTokenStream, NodeRange, ParserRange, Spacing, TokenCursor, + AttrsTarget, FlatTokenCursor, LazyAttrTokenStream, NodeRange, ParserRange, Spacing, }; use rustc_ast::{self as ast, AttrKind, AttrVec, Attribute, HasTokens}; use rustc_data_structures::fx::FxHashSet; @@ -19,7 +19,7 @@ use super::{Capturing, ForceCollect, Parser, Trailing}; #[derive(Clone, Debug)] pub(super) struct CollectPos { start_token: (Token, Spacing), - cursor_snapshot: TokenCursor, + cursor_snapshot: FlatTokenCursor, start_pos: u32, } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 34044e72ab92b..6f814280e1f18 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1062,8 +1062,8 @@ impl<'a> Parser<'a> { /// /// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`. // - // FIXME: With current `TokenCursor` it's hard to break tokens into more than 2 - // parts unless those parts are processed immediately. `TokenCursor` should either + // FIXME: With current `FlatTokenCursor` it's hard to break tokens into more than 2 + // parts unless those parts are processed immediately. `FlatTokenCursor` should either // support pushing "future tokens" (would be also helpful to `break_and_eat`), or // we should break everything including floats into more basic proc-macro style // tokens in the lexer (probably preferable). @@ -1138,7 +1138,7 @@ impl<'a> Parser<'a> { [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-')] | // 1.2e+3 | 1.2e-3 [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => { - // See the FIXME about `TokenCursor` above. + // See the FIXME about `FlatTokenCursor` above. self.error_unexpected_after_dot(); DestructuredFloat::Error } @@ -1275,6 +1275,11 @@ impl<'a> Parser<'a> { None }; let open_paren = self.token.span; + // Comparing depths across a bounded view's range end is normally a + // hazard (past the end `depth()` is 0, not the origin buffer's + // depth), but here it is benign: both samples come from the same + // cursor, and `call_depth` is >= 1 (taken inside the parens), so at + // the range end the equality fails just as the old cursor's did. let call_depth = self.token_cursor.depth(); let seq = match self.parse_expr_paren_seq() { @@ -2523,8 +2528,8 @@ impl<'a> Parser<'a> { } if self.token == TokenKind::Semi - && let Some((Delimiter::Parenthesis, _)) = self.token_cursor.parent_delim_and_span() && self.may_recover() + && self.token_cursor.enclosing_delimiter() == Some(Delimiter::Parenthesis) { // It is likely that the closure body is a block but where the // braces have been removed. We will recover and eat the next diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 0fa592459167a..22024e760e670 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2517,7 +2517,7 @@ impl<'a> Parser<'a> { fn parse_item_decl_macro(&mut self, lo: Span) -> PResult<'a, ItemKind> { let ident = self.parse_ident()?; let body = if self.check(exp!(OpenBrace)) { - self.parse_delim_args()? // `MacBody` + self.parse_delim_args_eager()? // `MacBody` } else if self.check(exp!(OpenParen)) { let params = self.parse_token_tree(); // `MacParams` let pspan = params.span(); @@ -2582,7 +2582,7 @@ impl<'a> Parser<'a> { self.dcx().emit_err(diagnostics::MacroNameRemoveBang { span }); } - let body = self.parse_delim_args()?; + let body = self.parse_delim_args_eager()?; self.eat_semi_for_macro_if_needed(&body, None); self.complain_if_pub_macro(vis, true); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index e2671a24177f1..3deaa218681fe 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -17,7 +17,7 @@ mod ty; pub mod asm; pub mod cfg_select; -use std::{debug_assert_matches, fmt, mem, slice}; +use std::{fmt, mem, slice}; use attr_wrapper::{AttrWrapper, UsePreAttrPos}; pub use diagnostics::AttemptLocalParseRecovery; @@ -29,8 +29,11 @@ pub use path::PathStyle; use rustc_ast::token::{ self, IdentIsRaw, InvisibleOrigin, MetaVarKind, NtExprKind, NtPatKind, Token, TokenKind, }; +#[cfg(debug_assertions)] +use rustc_ast::tokenstream::TokenCursor; use rustc_ast::tokenstream::{ - ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, WithTokens, + DelimSpan, FlatTokenCursor, FlatTt, ParserRange, ParserReplacement, Spacing, TokenStream, + TokenTree, WithTokens, }; use rustc_ast::util::case::Case; use rustc_ast::util::classify; @@ -194,7 +197,14 @@ pub struct Parser<'a> { pub capture_cfg: bool = false, restrictions: Restrictions = Restrictions::empty(), expected_token_types: TokenTypeSet = TokenTypeSet::new(), - token_cursor: TokenCursor, + token_cursor: FlatTokenCursor, + // Debug builds step the old tree-walking cursor in lockstep with + // `token_cursor` and assert in `bump` that both yield identical tokens. + // The shadow walks the token tree rebuilt from the cursor's own buffer, + // so a failure means either a cursor-stepping divergence or a buffer + // whose tree rebuild does not round-trip. + #[cfg(debug_assertions)] + shadow_cursor: Option = None, // The number of calls to `bump`, i.e. the position in the token stream. num_bump_calls: u32 = 0, // During parsing we may sometimes need to "unglue" a glued token into two @@ -245,8 +255,13 @@ pub struct Parser<'a> { // This type is used a lot, e.g. it's cloned when matching many declarative macro rules with // nonterminals. Make sure it doesn't unintentionally get bigger. We only check a few arches // though, because `TokenTypeSet(u128)` alignment varies on others, changing the total size. -#[cfg(all(target_pointer_width = "64", any(target_arch = "aarch64", target_arch = "x86_64")))] -rustc_data_structures::static_assert_size!(Parser<'_>, 288); +// Debug builds carry the extra shadow-cursor field, so only release sizes are asserted. +#[cfg(all( + target_pointer_width = "64", + any(target_arch = "aarch64", target_arch = "x86_64"), + not(debug_assertions) +))] +rustc_data_structures::static_assert_size!(Parser<'_>, 272); /// Stores span information about a closure. #[derive(Clone, Debug)] @@ -343,10 +358,28 @@ impl<'a> Parser<'a> { psess: &'a ParseSess, stream: TokenStream, subparser_name: Option<&'static str>, + ) -> Self { + // A stream that is a lazy view of a flat buffer can be parsed in + // place; only eager streams need the flatten pass. Callers that + // already hold a cursor (the lexer, mbe expansion) use + // `new_from_flat` directly and skip this dispatch. + let cursor = match stream.flat_view() { + Some(view) => FlatTokenCursor::from_view(view), + None => FlatTokenCursor::new(stream), + }; + Self::new_from_flat(psess, cursor, subparser_name) + } + + /// Like `new`, but takes an already-flattened token buffer, as produced + /// directly by the lexer for primary parses. + pub fn new_from_flat( + psess: &'a ParseSess, + token_cursor: FlatTokenCursor, + subparser_name: Option<&'static str>, ) -> Self { let mut parser = Parser { psess, - token_cursor: TokenCursor::new(stream), + token_cursor, subparser_name, capture_state: CaptureState { capturing: Capturing::No, @@ -357,6 +390,16 @@ impl<'a> Parser<'a> { .. }; + // Differential validation: rebuild the token tree from the buffer + // and walk it with the old tree-walking cursor alongside the flat + // one. Every parse in a debug build cross-checks the two, token for + // token, at each `bump`. + #[cfg(debug_assertions)] + { + parser.shadow_cursor = + Some(TokenCursor::new(parser.token_cursor.to_token_stream())); + } + // Make parser point to the first token. parser.bump(); @@ -494,18 +537,14 @@ 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. + // delimited sequence (false in the outermost token stream, which has no + // delimiters). Nested groups are stepped over via the match table, so + // this needs no unbounded token lookahead. // // Primarily used when `self.token` matches `OpenInvisible(_))`, to look // ahead through the current metavar expansion. fn check_noexpect_past_close_delim(&self, tok: &TokenKind) -> bool { - matches!( - self.token_cursor.look_ahead_past_close_delim(), - Some(TokenTree::Token(token::Token { kind, .. }, _)) if kind == tok - ) + self.token_cursor.token_after_enclosing_close().is_some_and(|after| after.kind == *tok) } /// Consumes a token 'tok' if it exists. Returns whether the given token was present. @@ -1105,6 +1144,13 @@ impl<'a> Parser<'a> { } /// Advance the parser by one token using provided token as the next one. + /// Advance to an injected token that does not come from the token + /// cursor (e.g. the second half of a broken compound token). + /// + /// Note: injected tokens must not be open delimiters. The token capture + /// machinery (`Parser::parse_token_tree_flat` via `current_group_slice`) + /// requires the current open delimiter to be backed by the buffer entry + /// just before the cursor position, and fails loudly otherwise. fn bump_with(&mut self, next: (Token, Spacing)) { self.inlined_bump_with(next) } @@ -1125,6 +1171,15 @@ impl<'a> Parser<'a> { // 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(); + #[cfg(debug_assertions)] + if let Some(shadow) = &mut self.shadow_cursor { + let tree_next = shadow.next(); + assert_eq!( + (next.0, next.1), + tree_next, + "flat token cursor diverged from the tree-walking cursor" + ); + } 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. @@ -1149,51 +1204,9 @@ impl<'a> Parser<'a> { return looker(&self.token); } - // 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) { - Some(tree) => { - // Indexing stayed within the current token tree. - match tree { - TokenTree::Token(token, _) => return looker(token), - &TokenTree::Delimited(dspan, _, delim, _) => { - if !delim.skip() { - return looker(&Token::new(delim.as_open_token_kind(), dspan.open)); - } - } - } - } - None => { - // The tree cursor lookahead went (one) past the end of the - // current token tree. Try to return a close delimiter. - if let Some((delim, span)) = self.token_cursor.parent_delim_and_span() - && !delim.skip() - { - // We are not in the outermost token stream, so we have - // delimiters. Also, those delimiters are not skipped. - return looker(&Token::new(delim.as_close_token_kind(), span.close)); - } - } - } - } - - // Just clone the token cursor and use `next`, 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; - if let token::OpenInvisible(origin) | token::CloseInvisible(origin) = token.kind - && origin.skip() - { - continue; - } - i += 1; - } - looker(&token) + // The flat token buffer makes lookahead a direct scan from the + // current position; delimiters are already materialized as tokens. + looker(&self.token_cursor.peek(dist)) } /// Like `look_ahead`, but skips over token trees rather than tokens. Useful @@ -1204,7 +1217,11 @@ impl<'a> Parser<'a> { looker: impl FnOnce(&TokenTree) -> R, ) -> Option { assert_ne!(dist, 0); - self.token_cursor.look_ahead(dist - 1).map(looker) + // Walk whole elements (tokens or delimited groups, including + // invisible ones) at the current nesting level; a delimited group + // carries a lazy view of its contents. Returns `None` when the + // current level ends first. + self.token_cursor.look_ahead_tree(dist).map(|tree| looker(&tree)) } /// Returns whether any of the given keywords are `dist` tokens ahead of the current one. @@ -1363,8 +1380,23 @@ impl<'a> Parser<'a> { } } + /// Parses delimited arguments whose token stream is a lazy view of the + /// flat token buffer. Used for macro invocation arguments, which usually + /// flow to the mbe matcher without the tree ever being needed. The view + /// retains the underlying buffer, so this should not be used for + /// long-lived nodes (attributes, macro definition bodies). fn parse_delim_args(&mut self) -> PResult<'a, Box> { - if let Some(args) = self.parse_delim_args_inner() { + if let Some(args) = self.parse_delim_args_inner(false) { + Ok(Box::new(args)) + } else { + self.unexpected_any() + } + } + + /// Parses delimited arguments with an eagerly materialized token tree, + /// for long-lived nodes that would otherwise pin the token buffer. + fn parse_delim_args_eager(&mut self) -> PResult<'a, Box> { + if let Some(args) = self.parse_delim_args_inner(true) { Ok(Box::new(args)) } else { self.unexpected_any() @@ -1372,7 +1404,7 @@ impl<'a> Parser<'a> { } fn parse_attr_args(&mut self) -> PResult<'a, AttrArgs> { - Ok(if let Some(args) = self.parse_delim_args_inner() { + Ok(if let Some(args) = self.parse_delim_args_inner(true) { AttrArgs::Delimited(args) } else if self.eat(exp!(Eq)) { let eq_span = self.prev_token.span; @@ -1383,48 +1415,66 @@ impl<'a> Parser<'a> { }) } - fn parse_delim_args_inner(&mut self) -> Option { + fn parse_delim_args_inner(&mut self, eager: bool) -> Option { let delimited = self.check(exp!(OpenParen)) || self.check(exp!(OpenBracket)) || self.check(exp!(OpenBrace)); delimited.then(|| { - let TokenTree::Delimited(dspan, _, delim, tokens) = self.parse_token_tree() else { - unreachable!() + let FlatTt::Slice(slice) = self.parse_token_tree_flat() else { + unreachable!("delimited args must be a delimited group") }; + let entries = slice.entries(); + let (open, close) = (entries.first().unwrap(), entries.last().unwrap()); + let dspan = DelimSpan::from_pair(open.token().span, close.token().span); + let delim = open.token().kind.open_delim().unwrap(); + let inner = slice.inner_view(); + let tokens = + if eager { inner.to_token_stream() } else { TokenStream::from_flat_view(inner) }; DelimArgs { dspan, delim, tokens } }) } /// Parses a single token tree from the input. pub fn parse_token_tree(&mut self) -> TokenTree { + self.parse_token_tree_flat().to_token_tree() + } + + /// Parses a single token tree from the input, in flat form: a delimited + /// group is captured as a slice of the token buffer (a refcount bump) + /// rather than rebuilt as a tree. Used for `tt` metavariable capture. + pub fn parse_token_tree_flat(&mut self) -> FlatTt { if self.token.kind.open_delim().is_some() { - // Clone the `TokenTree::Delimited` that we are currently - // within. That's what we are going to return. - let tree = self.token_cursor.clone_enclosing_delim(); - debug_assert_matches!(tree, TokenTree::Delimited(..)); - - // Advance the token cursor through the entire delimited - // sequence. After getting the `OpenDelim` we are *within* the - // delimited sequence, i.e. at depth `d`. After getting the - // matching `CloseDelim` we are *after* the delimited sequence, - // i.e. at depth `d - 1`. - let target_depth = self.token_cursor.depth() - 1; + // The current token is the open delimiter, so the entry that + // produced it is the one just before the cursor position; the + // delimited group it opens is what we are going to return. + let (slice, close_idx) = self.token_cursor.current_group_slice(&self.token); if let Capturing::No = self.capture_state.capturing { // We are not capturing tokens, so skip to the end of the // delimited sequence. This is a perf win when dealing with // declarative macros that pass large `tt` fragments through // multiple rules, as seen in the uom-0.37.0 crate. - self.token_cursor.bump_to_end(); + self.token_cursor.reposition_forward(close_idx); + // Keep the shadow in lockstep: it has descended into this + // group (its open delimiter is the current token), so + // skipping to the group's end mirrors the reposition above. + #[cfg(debug_assertions)] + if let Some(shadow) = &mut self.shadow_cursor { + shadow.bump_to_end(); + } self.bump(); - debug_assert_eq!(self.token_cursor.depth(), target_depth); } else { loop { - // Advance one token at a time, so `TokenCursor::next()` - // can capture these tokens if necessary. + // Advance one token at a time, so the token capture + // machinery can see these tokens if necessary. We have + // passed the whole sequence once the cursor moves beyond + // the close-delimiter entry; a depth comparison would be + // wrong for a bounded view cursor whose range ends at + // this group, since past the range end the origin + // buffer's depths are no longer visible. self.bump(); - if self.token_cursor.depth() == target_depth { + if self.token_cursor.position() > close_idx { break; } } @@ -1433,12 +1483,12 @@ impl<'a> Parser<'a> { // Consume close delimiter self.bump(); - tree + FlatTt::Slice(slice) } else { assert!(!self.token.kind.is_close_delim_or_eof()); let prev_spacing = self.token_spacing; self.bump(); - TokenTree::Token(self.prev_token, prev_spacing) + FlatTt::Token(self.prev_token, prev_spacing) } } @@ -1819,7 +1869,7 @@ impl<'a> Parser<'a> { // smaller. #[derive(Clone, Debug)] pub enum ParseNtResult { - Tt(TokenTree), + Tt(FlatTt), Ident(Ident, IdentIsRaw), Lifetime(Ident, IdentIsRaw), Item(Box), diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 9f9545c194082..5e97a1306d616 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -125,7 +125,7 @@ impl<'a> Parser<'a> { // we always capture tokens for any nonterminal that needs them. match kind { // Note that TT is treated differently to all the others. - NonterminalKind::TT => Ok(ParseNtResult::Tt(self.parse_token_tree())), + NonterminalKind::TT => Ok(ParseNtResult::Tt(self.parse_token_tree_flat())), NonterminalKind::Item => match self .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? { diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 5286873f3dc55..bc907acfa035e 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -6,8 +6,10 @@ use std::sync::{Arc, Mutex}; use std::{assert_matches, io, str}; use ast::token::IdentIsRaw; -use rustc_ast::token::{self, Delimiter, Token}; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::token::{self, Delimiter, InvisibleOrigin, Token}; +use rustc_ast::tokenstream::{ + DelimSpacing, DelimSpan, FlatTokenCursor, Spacing, TokenStream, TokenTree, +}; use rustc_ast::{self as ast, PatKind, visit}; use rustc_ast_pretty::pprust::item_to_string; use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; @@ -16,7 +18,7 @@ use rustc_errors::{AutoStream, DiagCtxt, MultiSpan, PResult}; use rustc_session::parse::ParseSess; use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::{ - BytePos, FileName, Pos, Span, Symbol, create_default_session_globals_then, kw, sym, + BytePos, DUMMY_SP, FileName, Pos, Span, Symbol, create_default_session_globals_then, kw, sym, }; use crate::lexer::StripTokens; @@ -2367,6 +2369,156 @@ fn string_to_tts_1() { }) } +#[test] +fn flat_round_trip() { + create_default_session_globals_then(|| { + for src in [ + "", + "a b c", + "fn a(b: i32) { b; }", + "macro_rules! zip (($a)=>($a));", + "a!{} b![] c!() d! { e! [ f!( ) ] }", + "x >>= y << z", + ] { + let stream = string_to_stream(src.to_string()); + let rebuilt = FlatTokenCursor::new(stream.clone()).to_token_stream(); + assert_eq!(stream, rebuilt, "flatten/rebuild round trip diverged for {src:?}"); + } + }) +} + +#[test] +fn lexer_buffer_is_flatten_fixed_point() { + // The lexer emits the flat buffer directly, with its own depth and + // match-table bookkeeping; `FlatTokenCursor::new` derives the same + // invariants from a token tree via `FlatSink::splice_stream`. The + // lexer's buffer must be a fixed point of rebuild-then-reflatten: + // entry-for-entry equal (tokens, spans, spacings, depths) with an + // identical match table. `flat_round_trip` above compares only the + // rebuilt *trees*, which cannot see depth or match divergence. + create_default_session_globals_then(|| { + let psess = ParseSess::new(); + for src in [ + "", + "a b c", + "fn a(b: i32) { b.c((d, [e]), f{g: h}); }", + "/// doc\nfn f() {}", + "x >>= y << z >> w", + "a!{ b![ (c) ] }", + "{} () []", + ] { + let source_file = psess + .source_map() + .new_source_file(FileName::anon_source_code(src), src.to_string()); + let lexed = crate::source_file_to_flat(&psess, source_file, None, StripTokens::Nothing) + .unwrap_or_else(|_| panic!("lexing failed for {src:?}")); + let reference = FlatTokenCursor::new(lexed.to_token_stream()); + let (lexed_entries, lexed_matches) = lexed.raw_parts(); + let (ref_entries, ref_matches) = reference.raw_parts(); + assert_eq!(lexed_entries, ref_entries, "lexer entries diverged for {src:?}"); + assert_eq!(lexed_matches, ref_matches, "lexer match table diverged for {src:?}"); + } + }) +} + +#[test] +fn flat_view_parses_group_ending_at_view_end() { + // Regression: a bounded view cursor returns depth 0 past its range end, + // so a `parse_token_tree` loop bounded on `depth()` would never + // terminate when the captured group ends exactly at the view end. The + // loop must be bounded on entry positions. + create_default_session_globals_then(|| { + let psess = ParseSess::new(); + let mut p = string_to_parser(&psess, "(a (b c))".to_string()); + let args = p.parse_delim_args().unwrap(); + assert!(args.tokens.flat_view().is_some()); + let mut inner = Parser::new(&psess, args.tokens.clone(), None); + assert!(matches!(inner.parse_token_tree(), TokenTree::Token(..))); + // `(b c)` ends exactly at the view end. + assert!(matches!(inner.parse_token_tree(), TokenTree::Delimited(..))); + assert_eq!(inner.token.kind, token::Eof); + }) +} + +#[test] +fn flat_view_debug_is_bounded() { + // Regression: Debug on a view-backed stream must print the viewed range + // only, not the whole underlying buffer. + create_default_session_globals_then(|| { + let psess = ParseSess::new(); + let mut p = string_to_parser(&psess, "(inside) outside_sentinel".to_string()); + let args = p.parse_delim_args().unwrap(); + let dump = format!("{:?}", args.tokens); + assert!(dump.contains("inside")); + assert!(!dump.contains("outside_sentinel"), "view Debug dumped the whole buffer: {dump}"); + }) +} + +#[test] +fn flat_view_eq_and_iter_len() { + create_default_session_globals_then(|| { + let psess = ParseSess::new(); + let mut p = string_to_parser(&psess, "(a (b c) d)".to_string()); + let args = p.parse_delim_args().unwrap(); + let flat = args.tokens.clone(); + assert!(flat.flat_view().is_some()); + // A view-backed stream and its eager rebuild compare equal, in both + // directions. + let eager: TokenStream = flat.iter().cloned().collect(); + assert!(eager.flat_view().is_none()); + assert_eq!(flat, eager); + assert_eq!(eager, flat); + // Iteration knows its exact length. + let mut iter = flat.iter(); + assert_eq!(iter.len(), 3); + iter.next(); + assert_eq!(iter.len(), 2); + }) +} + +#[test] +fn tree_look_ahead_counts_leading_invisible_group() { + // Regression: tree-level lookahead must treat a *skipped* invisible + // group right at the cursor as one whole element, exactly like the old + // tree-walking cursor treated the corresponding `Delimited`. Filtering + // the open entry out (the way plain token consumption does) enters the + // group transparently and pairs every subsequent delimiter-counting walk + // one nesting level too deep. + create_default_session_globals_then(|| { + let psess = ParseSess::new(); + let stream = TokenStream::new(vec![ + TokenTree::token_alone(token::Ident(Symbol::intern("x"), IdentIsRaw::No), DUMMY_SP), + TokenTree::Delimited( + DelimSpan::from_single(DUMMY_SP), + DelimSpacing::new(Spacing::Alone, Spacing::Alone), + Delimiter::Invisible(InvisibleOrigin::ProcMacro), + TokenStream::new(vec![TokenTree::token_alone( + token::Ident(Symbol::intern("inside"), IdentIsRaw::No), + DUMMY_SP, + )]), + ), + TokenTree::token_alone(token::Ident(Symbol::intern("y"), IdentIsRaw::No), DUMMY_SP), + ]); + let parser = Parser::new(&psess, stream, None); + // The parser sits on `x`; the invisible group is the next element. + assert!(parser.token.is_ident_named(Symbol::intern("x"))); + let next = parser.tree_look_ahead(1, |tt| match tt { + TokenTree::Delimited(.., Delimiter::Invisible(InvisibleOrigin::ProcMacro), inner) => { + // The group's contents are a real (lazy) view, not a stub. + inner.len() == 1 + } + _ => false, + }); + assert_eq!(next, Some(true)); + // The element after the group is `y`, one step past it. + let after = parser.tree_look_ahead(2, |tt| match tt { + TokenTree::Token(tok, _) => tok.is_ident_named(Symbol::intern("y")), + _ => false, + }); + assert_eq!(after, Some(true)); + }) +} + #[test] fn parse_use() { create_default_session_globals_then(|| {