From 318738969de72e2fa15c2ad889ff905e868424fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 25 Aug 2026 11:33:44 +0200 Subject: [PATCH 01/25] Lex tokens into a flat arena To avoid unnecessary allocations during lexing. --- compiler/rustc_ast/src/lib.rs | 1 + compiler/rustc_ast/src/tokenarena.rs | 136 +++++++++++++++++++ compiler/rustc_parse/src/lexer/mod.rs | 8 +- compiler/rustc_parse/src/lexer/tokentrees.rs | 40 +++--- 4 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 compiler/rustc_ast/src/tokenarena.rs diff --git a/compiler/rustc_ast/src/lib.rs b/compiler/rustc_ast/src/lib.rs index 46d8e11cc0931..e0a0a644cd926 100644 --- a/compiler/rustc_ast/src/lib.rs +++ b/compiler/rustc_ast/src/lib.rs @@ -31,6 +31,7 @@ pub mod format; pub mod mut_visit; pub mod node_id; pub mod token; +pub mod tokenarena; pub mod tokenstream; pub mod visit; diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs new file mode 100644 index 0000000000000..536cfc9ca6443 --- /dev/null +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -0,0 +1,136 @@ +use rustc_macros::{Decodable, Encodable, StableHash}; + +use crate::token::{Delimiter, Token}; +use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; + +/// Part of a `TokenArena`. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +pub enum ArenaTokenTree { + /// A single token. Should never be `OpenDelim` or `CloseDelim`, because + /// delimiters are implicitly represented by `Delimited`. + Token(Token, Spacing), + /// A delimited sequence of token trees. + Delimited(DelimitedBounds, DelimitedData), +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] +pub struct TokenArena { + tokens: Vec, +} + +impl TokenArena { + pub fn new(tokens: Vec) -> Self { + Self { tokens } + } + + pub fn push(&mut self, token: ArenaTokenTree) { + self.tokens.push(token); + } + + pub fn start_delimited(&mut self) -> OpenDelimited { + let index = self.length(); + self.tokens.push(ArenaTokenTree::Delimited( + DelimitedBounds { start: index as u32, length: 0 }, + DelimitedData { + span: DelimSpan { open: Default::default(), close: Default::default() }, + spacing: DelimSpacing { open: Spacing::Alone, close: Spacing::Alone }, + delimiter: Delimiter::Parenthesis, + }, + )); + OpenDelimited { start: index } + } + + pub fn finish_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { + let length = self.length(); + match &mut self.tokens[open.start] { + ArenaTokenTree::Token(_, _) => unreachable!("Called finish_delimited on a token"), + ArenaTokenTree::Delimited(bounds, data) => { + let len = length.saturating_sub(open.start); + bounds.length = len as u32; + *data = delimited_data; + } + } + } + + pub fn get_item_at(&self, index: usize) -> Option<&ArenaTokenTree> { + self.tokens.get(index) + } + + pub fn length(&self) -> usize { + self.tokens.len() + } + + pub fn from_stream(stream: &TokenStream) -> Self { + let mut arena = TokenArena { tokens: Vec::with_capacity(stream.len()) }; + arena.fill(stream); + arena + } + + fn fill(&mut self, stream: &TokenStream) { + for item in stream.iter() { + match item { + TokenTree::Token(token, spacing) => { + self.tokens.push(ArenaTokenTree::Token(*token, *spacing)); + } + TokenTree::Delimited(span, spacing, delimiter, stream) => { + let start = self.start_delimited(); + self.fill(stream); + self.finish_delimited( + start, + DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, + ); + } + } + } + } + + pub fn to_token_stream(&self) -> TokenStream { + fn to_token_stream(arena: &TokenArena, start: usize, length: usize) -> TokenStream { + let mut tokens = Vec::new(); + let mut index = start; + let end = start + length; + while index < end { + match &arena.tokens[index] { + ArenaTokenTree::Token(a, b) => { + tokens.push(TokenTree::Token(*a, *b)); + index += 1; + } + ArenaTokenTree::Delimited(bounds, data) => { + let tokenstream = to_token_stream( + arena, + (bounds.start + 1) as usize, + (bounds.length as usize).saturating_sub(1), + ); + tokens.push(TokenTree::Delimited( + data.span, + data.spacing, + data.delimiter, + tokenstream, + )); + index += bounds.length as usize; + } + } + } + + TokenStream::new(tokens) + } + to_token_stream(&self, 0, self.tokens.len()) + } +} + +pub struct OpenDelimited { + start: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +pub struct DelimitedBounds { + pub start: u32, + pub length: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +pub struct DelimitedData { + pub span: DelimSpan, + pub spacing: DelimSpacing, + pub delimiter: Delimiter, +} diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 6ed61a9f4e01d..5ede1693aa7f2 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -1,6 +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::tokenarena::TokenArena; use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::unicode::{TEXT_FLOW_CONTROL_CHARS, contains_text_flow_control_chars}; use rustc_errors::codes::*; @@ -98,15 +99,16 @@ pub(crate) fn lex_token_trees<'psess, 'src>( token: Token::dummy(), diag_info: TokenTreeDiagInfo::default(), }; - let res = lexer.lex_token_trees(/* is_delimited */ false); + let mut arena = TokenArena::new(Vec::new()); + let res = lexer.lex_token_trees(&mut arena, /* is_delimited */ false); 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(_) => { if unmatched_closing_delims.is_empty() { - Ok(stream) + Ok(arena.to_token_stream()) } 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 3455947471503..d6c4e1a1fc207 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -1,5 +1,6 @@ use rustc_ast::token::{self, Delimiter, Token}; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenTree, DelimitedData, TokenArena}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing}; use rustc_ast_pretty::pprust::token_to_string; use rustc_errors::Diag; @@ -13,48 +14,47 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // opening delimiter. pub(super) fn lex_token_trees( &mut self, + arena: &mut TokenArena, is_delimited: bool, - ) -> Result<(Spacing, TokenStream), Diag<'psess>> { + ) -> 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, + let delimited = arena.start_delimited(); + let value = match self.lex_token_tree_open_delim(arena, delim) { + Ok(value) => value, Err(errs) => return Err(errs), - }) + }; + arena.finish_delimited(delimited, value); } 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)); + arena.push(ArenaTokenTree::Token(this_tok, this_spacing)); } } } fn lex_token_tree_open_delim( &mut self, + arena: &mut TokenArena, open_delim: Delimiter, - ) -> Result> { + ) -> Result> { // The span for beginning of the delimited section. let pre_span = self.token.span; @@ -63,7 +63,11 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // 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)?; + + // We remember where we were in the arena, so that we can check how many trees were parsed + let index = arena.length(); + let open_spacing = self.lex_token_trees(arena, /* is_delimited */ true)?; + let lexed_trees = arena.length() - index; // Expand to cover the entire delimited token tree. let delim_span = DelimSpan::from_pair(pre_span, self.token.span); @@ -75,7 +79,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 lexed_trees == 0 && 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 @@ -93,7 +97,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // A brace-delimited block whose first token is `&&`/`||` usually means // the user meant to continue an if-let chain, e.g. `if let P = e { && cond {`. if Delimiter::Brace == open_delim - && let Some(TokenTree::Token(tok, _)) = tts.iter().next() + && let Some(ArenaTokenTree::Token(tok, _)) = arena.get_item_at(index) && matches!(tok.kind, token::AndAnd | token::OrOr) { self.diag_info.if_let_chain_hint_spans.push(tok.span); @@ -159,7 +163,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { let spacing = DelimSpacing::new(open_spacing, close_spacing); - Ok(TokenTree::Delimited(delim_span, spacing, open_delim, tts)) + Ok(DelimitedData { span: delim_span, spacing, delimiter: open_delim }) } // Move on to the next token, returning the current token and its spacing. From 4d0cd74c84abc9ad480627ff158b424388367364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 25 Aug 2026 13:43:50 +0200 Subject: [PATCH 02/25] Pass `TokenArena` to `Parser` --- compiler/rustc_ast/src/tokenarena.rs | 4 ++++ compiler/rustc_ast/src/tokenstream.rs | 4 +++- .../rustc_attr_parsing/src/attributes/cfg.rs | 3 ++- compiler/rustc_attr_parsing/src/parser.rs | 7 ++++--- .../rustc_attr_parsing/src/validate_attr.rs | 6 ++++-- compiler/rustc_builtin_macros/src/cfg_eval.rs | 4 +++- compiler/rustc_expand/src/base.rs | 3 ++- compiler/rustc_expand/src/expand.rs | 5 +++++ compiler/rustc_expand/src/mbe/macro_rules.rs | 8 +++++--- compiler/rustc_expand/src/proc_macro.rs | 7 ++++++- .../rustc_expand/src/proc_macro_server.rs | 8 +++++++- compiler/rustc_parse/src/lexer/mod.rs | 5 ++--- compiler/rustc_parse/src/lib.rs | 19 ++++++++++--------- compiler/rustc_parse/src/parser/mod.rs | 5 +++-- 14 files changed, 60 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 536cfc9ca6443..f4904b7ec0089 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -60,6 +60,10 @@ impl TokenArena { self.tokens.len() } + pub fn is_empty(&self) -> bool { + self.tokens.is_empty() + } + pub fn from_stream(stream: &TokenStream) -> Self { let mut arena = TokenArena { tokens: Vec::with_capacity(stream.len()) }; arena.fill(stream); diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index df71aad0111cd..7d088b7aff070 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -20,6 +20,7 @@ use thin_vec::ThinVec; use crate::ast::AttrStyle; use crate::ast_traits::HasTokens; use crate::token::{self, Delimiter, Token, TokenKind}; +use crate::tokenarena::TokenArena; use crate::{AttrVec, Attribute}; #[cfg(test)] @@ -969,7 +970,8 @@ pub struct TokenCursor { impl TokenCursor { #[inline] - pub fn new(stream: TokenStream) -> Self { + pub fn new(arena: TokenArena) -> Self { + let stream = arena.to_token_stream(); TokenCursor { curr: TokenTreeCursor::new(stream), stack: vec![] } } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 8102208ec519d..b50bd485d342d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -1,6 +1,7 @@ use std::convert::identity; use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::{DelimSpan, WithTokens}; use rustc_ast::{AttrItem, Attribute, LitKind, ast, token}; use rustc_attr_ir::target::Target; @@ -309,7 +310,7 @@ pub fn parse_cfg_attr( match &cfg_attr.get_normal_item().args { ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => { check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim); - match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| { + match parse_in(&sess.psess, TokenArena::from_stream(tokens), "`cfg_attr` input", |p| { parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr) }) { Ok(r) => return Some(r), diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 8efe5bf4f1f90..d57a3ddf316e8 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -14,6 +14,7 @@ use std::fmt::{Debug, Display}; use std::sync::atomic::{AtomicBool, Ordering}; use rustc_ast::token::{self, Delimiter, MetaVarKind}; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, PathSegment, StmtKind, UnOp, @@ -721,13 +722,13 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { } fn parse( - tokens: TokenStream, + arena: TokenArena, psess: &'sess ParseSess, span: Span, should_emit: ShouldEmit, allow_expr_metavar: AllowExprMetavar, ) -> PResult<'sess, MetaItemListParser> { - let mut parser = Parser::new(psess, tokens, None); + let mut parser = Parser::new(psess, arena, None); if let ShouldEmit::ErrorsAndLints { recovery } = should_emit { parser = parser.recovery(recovery); } @@ -764,7 +765,7 @@ impl MetaItemListParser { allow_expr_metavar: AllowExprMetavar, ) -> Result> { MetaItemListParserContext::parse( - tokens.clone(), + TokenArena::from_stream(tokens), psess, span, should_emit, diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index 4719ee5103877..6cd12ef09db79 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -4,6 +4,7 @@ use std::convert::identity; use std::slice; use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{ self as ast, AttrArgs, AttrKind, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, @@ -75,8 +76,9 @@ pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Met AttrArgs::Empty => MetaItemKind::Word, AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { check_meta_bad_delim(psess, *dspan, *delim); - let nmis = - parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?; + let nmis = parse_in(psess, TokenArena::from_stream(tokens), "meta list", |p| { + p.parse_meta_seq_top() + })?; MetaItemKind::List(nmis) } AttrArgs::Eq { expr, .. } => { diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 34ddd9427cdde..0cb583c77ff52 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -2,6 +2,7 @@ use core::ops::ControlFlow; use rustc_ast as ast; use rustc_ast::mut_visit::MutVisitor; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{Attribute, HasTokens, NodeId, mut_visit, visit}; use rustc_errors::PResult; @@ -105,7 +106,8 @@ impl CfgEval<'_> { // // After that we have our re-parsed `AttrTokenStream`, recursively configuring // our attribute target will correctly configure the tokens as well. - let mut parser = Parser::new(&self.0.sess.psess, orig_tokens, None); + let mut parser = + Parser::new(&self.0.sess.psess, TokenArena::from_stream(&orig_tokens), None); parser.capture_cfg = true; let res: PResult<'_, Option> = try { match &annotatable { diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index fda75319b087b..d48fb3bdacb06 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast::attr::MarkedAttrs; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind, Safety}; @@ -1258,7 +1259,7 @@ impl<'a> ExtCtxt<'a> { expand::MacroExpander::new(self, true) } pub fn new_parser_from_tts(&self, stream: TokenStream) -> Parser<'a> { - Parser::new(&self.sess.psess, stream, MACRO_ARGUMENTS) + Parser::new(&self.sess.psess, TokenArena::from_stream(&stream), MACRO_ARGUMENTS) } pub fn source_map(&self) -> &'a SourceMap { self.sess.psess.source_map() diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c58629111ac00..586adc7698b93 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -777,6 +777,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { // we are invoking it on an out-of-line module or crate. Annotatable::Crate(krate) => { rustc_parse::fake_token_stream_for_crate(&self.cx.sess.psess, krate) + .to_token_stream() } Annotatable::Item(item_inner) if matches!(attr.style, AttrStyle::Inner) @@ -795,6 +796,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, Some(&attr), ) + .to_token_stream() } Annotatable::Item(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_item( @@ -802,6 +804,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, None, ) + .to_token_stream() } // When a function has EII implementations attached (via `eii_impl`), // use fake tokens so the pretty-printer re-emits the EII attribute @@ -818,12 +821,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, None, ) + .to_token_stream() } Annotatable::ForeignItem(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_foreign_item( &self.cx.sess.psess, item_inner, ) + .to_token_stream() } _ => item.to_tokens(), }; diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index b268b8b767327..d8018ec15d392 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -7,6 +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::tokenarena::TokenArena; use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId, Safety}; use rustc_ast_pretty::pprust; @@ -132,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(&cx.sess.psess, TokenArena::from_stream(&tts), 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 @@ -796,7 +797,7 @@ pub fn compile_declarative_macro( let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; - let body = macro_def.body.tokens.clone(); + let body = TokenArena::from_stream(¯o_def.body.tokens); let mut p = Parser::new(&sess.psess, body, rustc_parse::MACRO_ARGUMENTS); // Don't abort iteration early, so that multiple errors can be reported. We only abort early on @@ -1869,5 +1870,6 @@ pub(super) fn parser_from_cx( recovery: Recovery, ) -> Parser<'_> { tts.desugar_doc_comments(); - Parser::new(psess, tts, rustc_parse::MACRO_ARGUMENTS).recovery(recovery) + Parser::new(psess, TokenArena::from_stream(&tts), rustc_parse::MACRO_ARGUMENTS) + .recovery(recovery) } diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 5e01b851b75c7..18379d61c8e0e 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,4 +1,5 @@ use rustc_ast as ast; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::TokenStream; use rustc_data_structures::AtomicRef; use rustc_data_structures::profiling::TimingGuard; @@ -124,7 +125,11 @@ impl MultiItemModifier for DeriveProcMacro { }; let error_count_before = ecx.dcx().err_count(); - let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive")); + let mut parser = Parser::new( + &ecx.sess.psess, + TokenArena::from_stream(&output), + Some("proc-macro derive"), + ); let mut items = vec![]; loop { diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index c522626b39562..373f24a97ea7a 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -2,6 +2,7 @@ use std::ops::{Bound, Range}; use rustc_ast as ast; use rustc_ast::token as tk; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::{self, DelimSpacing, Spacing, TokenStream}; use rustc_ast::util::literal::escape_byte_str_symbol; use rustc_ast_pretty::pprust; @@ -576,6 +577,7 @@ impl server::Server for Rustc<'_, '_> { src.to_string(), Some(self.call_site), ) + .map(|arena| arena.to_token_stream()) }) .map_err(|_| String::from("failed to parse to tokenstream"))? .map_err(cancel_diags_into_string) @@ -588,7 +590,11 @@ impl server::Server for Rustc<'_, '_> { fn ts_expand_expr(&mut self, stream: &Self::TokenStream) -> Result { // Parse the expression from our tokenstream. let expr = try { - let mut p = Parser::new(self.psess(), stream.clone(), Some("proc_macro expand expr")); + let mut p = Parser::new( + self.psess(), + TokenArena::from_stream(stream), + Some("proc_macro expand expr"), + ); let expr = p.parse_expr()?; if p.token != tk::Eof { p.unexpected()?; diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 5ede1693aa7f2..7cac44aff9d51 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -2,7 +2,6 @@ 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::tokenarena::TokenArena; -use rustc_ast::tokenstream::TokenStream; 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}; @@ -70,7 +69,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) { @@ -108,7 +107,7 @@ pub(crate) fn lex_token_trees<'psess, 'src>( match res { Ok(_) => { if unmatched_closing_delims.is_empty() { - Ok(arena.to_token_stream()) + Ok(arena) } else { // Return error if there are unmatched delimiters or unclosed delimiters. Err(unmatched_closing_delims) diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 539c15f18a9a4..1f0416f8dc102 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -29,6 +29,7 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); #[macro_use] pub mod parser; use parser::Parser; +use rustc_ast::tokenarena::TokenArena; use crate::lexer::StripTokens; @@ -245,7 +246,7 @@ pub fn source_str_to_stream( name: FileName, source: String, override_span: Option, -) -> Result>> { +) -> Result>> { let source_file = psess.source_map().new_source_file(name, source); // FIXME(frontmatter): Consider stripping frontmatter in a future edition. We can't strip them // in the current edition since that would be breaking. @@ -262,7 +263,7 @@ fn source_file_to_stream<'psess>( source_file: Arc, override_span: Option, strip_tokens: StripTokens, -) -> Result>> { +) -> Result>> { let src = source_file.src.as_ref().unwrap_or_else(|| { psess.dcx().bug(format!( "cannot lex `source_file` without source: {}", @@ -276,11 +277,11 @@ fn source_file_to_stream<'psess>( /// Runs the given subparser `f` on the tokens of the given `attr`'s item. pub fn parse_in<'a, T>( psess: &'a ParseSess, - tts: TokenStream, + arena: TokenArena, name: &'static str, mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>, ) -> PResult<'a, T> { - let mut parser = Parser::new(psess, tts, Some(name)); + let mut parser = Parser::new(psess, arena, Some(name)); let result = f(&mut parser)?; if parser.token != token::Eof { parser.unexpected()?; @@ -292,9 +293,9 @@ pub fn fake_token_stream_for_item( psess: &ParseSess, item: &ast::Item, attr_to_exclude: Option<&ast::Attribute>, -) -> TokenStream { +) -> TokenArena { if let Some(tokens) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { - return tokens; + return TokenArena::from_stream(&tokens); } let source = pprust::item_to_string(item); @@ -350,7 +351,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(arena) => arena.to_token_stream(), Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); return None; @@ -362,13 +363,13 @@ fn lex_token_trees_for_span( pub fn fake_token_stream_for_foreign_item( psess: &ParseSess, item: &ast::ForeignItem, -) -> TokenStream { +) -> TokenArena { let source = pprust::foreign_item_to_string(item); let filename = FileName::macro_expansion_source_code(&source); unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))) } -pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenStream { +pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenArena { let source = pprust::crate_to_string_for_macros(krate); let filename = FileName::macro_expansion_source_code(&source); unwrap_or_emit_fatal(source_str_to_stream( diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 80c1eeb4ef041..e6d4d177fbb9c 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -29,6 +29,7 @@ pub use path::PathStyle; use rustc_ast::token::{ self, IdentIsRaw, InvisibleOrigin, MetaVarKind, NtExprKind, NtPatKind, Token, TokenKind, }; +use rustc_ast::tokenarena::TokenArena; use rustc_ast::tokenstream::{ ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, WithTokens, }; @@ -342,12 +343,12 @@ pub fn token_descr(token: &Token) -> String { impl<'a> Parser<'a> { pub fn new( psess: &'a ParseSess, - stream: TokenStream, + arena: TokenArena, subparser_name: Option<&'static str>, ) -> Self { let mut parser = Parser { psess, - token_cursor: TokenCursor::new(stream), + token_cursor: TokenCursor::new(arena), subparser_name, capture_state: CaptureState { capturing: Capturing::No, From 291d5bc8eaf357e7672773b06210e861e05080eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 12:46:56 +0200 Subject: [PATCH 03/25] Explicitly store delimited sequence end markers in the token arena To make it easier to track delimited sequences. --- compiler/rustc_ast/src/tokenarena.rs | 29 +++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index f4904b7ec0089..62b273bc374f4 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,3 +1,4 @@ +use rustc_index::static_assert_size; use rustc_macros::{Decodable, Encodable, StableHash}; use crate::token::{Delimiter, Token}; @@ -7,12 +8,15 @@ use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTre #[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub enum ArenaTokenTree { /// A single token. Should never be `OpenDelim` or `CloseDelim`, because - /// delimiters are implicitly represented by `Delimited`. + /// delimiters are implicitly represented by `DelimitedStart`/`DelimitedEnd`. Token(Token, Spacing), /// A delimited sequence of token trees. - Delimited(DelimitedBounds, DelimitedData), + DelimitedStart(DelimitedBounds, DelimitedData), + DelimitedEnd(DelimitedBounds, DelimitedData), } +static_assert_size!(ArenaTokenTree, 36); + #[derive(Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] pub struct TokenArena { tokens: Vec, @@ -29,7 +33,7 @@ impl TokenArena { pub fn start_delimited(&mut self) -> OpenDelimited { let index = self.length(); - self.tokens.push(ArenaTokenTree::Delimited( + self.tokens.push(ArenaTokenTree::DelimitedStart( DelimitedBounds { start: index as u32, length: 0 }, DelimitedData { span: DelimSpan { open: Default::default(), close: Default::default() }, @@ -42,9 +46,15 @@ impl TokenArena { pub fn finish_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { let length = self.length(); + self.tokens.push(ArenaTokenTree::DelimitedEnd( + DelimitedBounds { start: open.start as u32, length: length as u32 }, + delimited_data, + )); match &mut self.tokens[open.start] { - ArenaTokenTree::Token(_, _) => unreachable!("Called finish_delimited on a token"), - ArenaTokenTree::Delimited(bounds, data) => { + tree @ (ArenaTokenTree::Token(..) | ArenaTokenTree::DelimitedEnd(..)) => { + unreachable!("Called finish_delimited on an invalid tree type {tree:?}") + } + ArenaTokenTree::DelimitedStart(bounds, data) => { let len = length.saturating_sub(open.start); bounds.length = len as u32; *data = delimited_data; @@ -99,7 +109,7 @@ impl TokenArena { tokens.push(TokenTree::Token(*a, *b)); index += 1; } - ArenaTokenTree::Delimited(bounds, data) => { + ArenaTokenTree::DelimitedStart(bounds, data) => { let tokenstream = to_token_stream( arena, (bounds.start + 1) as usize, @@ -113,6 +123,9 @@ impl TokenArena { )); index += bounds.length as usize; } + ArenaTokenTree::DelimitedEnd(..) => { + index += 1; + } } } @@ -129,10 +142,12 @@ pub struct OpenDelimited { #[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub struct DelimitedBounds { pub start: u32, + /// The length includes both the start and the end token. + /// So an empty delimited sequence has length 2. pub length: u32, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub struct DelimitedData { pub span: DelimSpan, pub spacing: DelimSpacing, From 18b2e4ea76839dd61d1192511fe27f02d258fe67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 14:01:36 +0200 Subject: [PATCH 04/25] Reimplement the parser's token cursor to work on top of the arena --- compiler/rustc_ast/src/tokenarena.rs | 123 ++++++++----- compiler/rustc_ast/src/tokenstream.rs | 181 +++++++++---------- compiler/rustc_parse/src/lexer/tokentrees.rs | 2 +- compiler/rustc_parse/src/parser/function.rs | 17 +- compiler/rustc_parse/src/parser/item.rs | 5 +- compiler/rustc_parse/src/parser/mod.rs | 19 +- 6 files changed, 190 insertions(+), 157 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 62b273bc374f4..02c0a905ef397 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -5,14 +5,29 @@ use crate::token::{Delimiter, Token}; use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; /// Part of a `TokenArena`. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub enum ArenaTokenTree { /// A single token. Should never be `OpenDelim` or `CloseDelim`, because /// delimiters are implicitly represented by `DelimitedStart`/`DelimitedEnd`. Token(Token, Spacing), /// A delimited sequence of token trees. DelimitedStart(DelimitedBounds, DelimitedData), - DelimitedEnd(DelimitedBounds, DelimitedData), + // TODO: get rid of this and represent it implicitly + DelimitedEnd, +} + +impl ArenaTokenTree { + /// Convert an arena token tree to the tree-shaped token tree. + pub fn to_token_tree(&self, arena: &TokenArena) -> TokenTree { + match self { + ArenaTokenTree::Token(token, spacing) => TokenTree::Token(*token, *spacing), + ArenaTokenTree::DelimitedStart(bounds, data) => { + let tts = arena.iter_delimited(bounds).map(|tt| tt.to_token_tree(arena)).collect(); + TokenTree::Delimited(data.span, data.spacing, data.delimiter, TokenStream::new(tts)) + } + ArenaTokenTree::DelimitedEnd => unreachable!(), + } + } } static_assert_size!(ArenaTokenTree, 36); @@ -31,6 +46,51 @@ impl TokenArena { self.tokens.push(token); } + /// Iter top-level token trees of a delimited token sequence. + pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { + let mut index = (bounds.start + 1) as usize; + let end = bounds.index_of_next_token_tree().saturating_sub(1); + std::iter::from_fn(move || { + if index >= end { + return None; + } + let item = self.get_innermost_elem_at(index)?; + match item { + token @ ArenaTokenTree::Token(..) => { + index += 1; + Some(*token) + } + tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { + index = bounds.index_of_next_token_tree(); + Some(*tree) + } + ArenaTokenTree::DelimitedEnd => unreachable!(), + } + }) + } + + pub fn iter_top_level_trees(&self) -> impl Iterator { + let mut index = 0; + let end = self.tokens.len(); + std::iter::from_fn(move || { + if index >= end { + return None; + } + let item = self.get_innermost_elem_at(index)?; + match item { + token @ ArenaTokenTree::Token(..) => { + index += 1; + Some(*token) + } + tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { + index = bounds.index_of_next_token_tree(); + Some(*tree) + } + ArenaTokenTree::DelimitedEnd => unreachable!(), + } + }) + } + pub fn start_delimited(&mut self) -> OpenDelimited { let index = self.length(); self.tokens.push(ArenaTokenTree::DelimitedStart( @@ -45,13 +105,10 @@ impl TokenArena { } pub fn finish_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { + self.tokens.push(ArenaTokenTree::DelimitedEnd); let length = self.length(); - self.tokens.push(ArenaTokenTree::DelimitedEnd( - DelimitedBounds { start: open.start as u32, length: length as u32 }, - delimited_data, - )); match &mut self.tokens[open.start] { - tree @ (ArenaTokenTree::Token(..) | ArenaTokenTree::DelimitedEnd(..)) => { + tree @ (ArenaTokenTree::Token(..) | ArenaTokenTree::DelimitedEnd) => { unreachable!("Called finish_delimited on an invalid tree type {tree:?}") } ArenaTokenTree::DelimitedStart(bounds, data) => { @@ -62,7 +119,7 @@ impl TokenArena { } } - pub fn get_item_at(&self, index: usize) -> Option<&ArenaTokenTree> { + pub fn get_innermost_elem_at(&self, index: usize) -> Option<&ArenaTokenTree> { self.tokens.get(index) } @@ -99,39 +156,11 @@ impl TokenArena { } pub fn to_token_stream(&self) -> TokenStream { - fn to_token_stream(arena: &TokenArena, start: usize, length: usize) -> TokenStream { - let mut tokens = Vec::new(); - let mut index = start; - let end = start + length; - while index < end { - match &arena.tokens[index] { - ArenaTokenTree::Token(a, b) => { - tokens.push(TokenTree::Token(*a, *b)); - index += 1; - } - ArenaTokenTree::DelimitedStart(bounds, data) => { - let tokenstream = to_token_stream( - arena, - (bounds.start + 1) as usize, - (bounds.length as usize).saturating_sub(1), - ); - tokens.push(TokenTree::Delimited( - data.span, - data.spacing, - data.delimiter, - tokenstream, - )); - index += bounds.length as usize; - } - ArenaTokenTree::DelimitedEnd(..) => { - index += 1; - } - } - } - - TokenStream::new(tokens) + let mut tokens = vec![]; + for tt in self.iter_top_level_trees() { + tokens.push(tt.to_token_tree(self)); } - to_token_stream(&self, 0, self.tokens.len()) + TokenStream::new(tokens) } } @@ -139,7 +168,7 @@ pub struct OpenDelimited { start: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub struct DelimitedBounds { pub start: u32, /// The length includes both the start and the end token. @@ -147,6 +176,18 @@ pub struct DelimitedBounds { pub length: u32, } +impl DelimitedBounds { + /// Return the index of the next token tree that follows this delimited token sequence. + pub fn index_of_next_token_tree(&self) -> usize { + (self.start + self.length) as usize + } + + /// Return the index of the closing delimiter of this token sequence. + pub fn index_of_closing_delimiter(&self) -> usize { + self.index_of_next_token_tree().saturating_sub(1) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub struct DelimitedData { pub span: DelimSpan, diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 7d088b7aff070..16d9cd9be90c3 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -20,7 +20,7 @@ use thin_vec::ThinVec; use crate::ast::AttrStyle; use crate::ast_traits::HasTokens; use crate::token::{self, Delimiter, Token, TokenKind}; -use crate::tokenarena::TokenArena; +use crate::tokenarena::{ArenaTokenTree, DelimitedBounds, DelimitedData, TokenArena}; use crate::{AttrVec, Attribute}; #[cfg(test)] @@ -901,78 +901,20 @@ impl<'t> Iterator for TokenStreamIter<'t> { } } -#[derive(Clone, Debug)] -struct TokenTreeCursor { - stream: TokenStream, - /// Points to the next token tree (or one past the end of the stream). - next_idx: usize, -} - -impl TokenTreeCursor { - #[inline] - fn new(stream: TokenStream) -> Self { - TokenTreeCursor { stream, next_idx: 0 } - } - - /// Gets the current token tree within this cursor. In a debug build it panics on a cursor that - /// hasn't been bumped; in a release build it will return `None`. - #[inline] - fn curr(&self) -> Option<&TokenTree> { - debug_assert!(self.next_idx > 0); - self.stream.get(self.next_idx - 1) - } - - /// Gets the next token tree without advancing. - #[inline] - fn next(&self) -> Option<&TokenTree> { - self.stream.get(self.next_idx) - } - - /// Gets the token tree `n` ahead. `look_ahead(1)` is equivalent to `next()`. `look_ahead(0)` - /// isn't allowed and will panic. - #[inline] - fn look_ahead(&self, n: usize) -> Option<&TokenTree> { - assert_ne!(n, 0); - self.stream.get(self.next_idx + (n - 1)) - } - - /// Move the cursor to the next token tree. - #[inline] - fn bump(&mut self) { - self.next_idx += 1; - } - - /// For skipping ahead in rare circumstances. - #[inline] - fn bump_to_end(&mut self) { - self.next_idx = self.stream.len(); - } -} - -/// A `TokenStream` cursor that produces `Token`s. It's a bit odd that -/// we (a) lex tokens into a nice tree structure (`TokenStream`), and then (b) -/// use this type to emit them as a linear sequence. But a linear sequence is -/// what the parser expects, for the most part. +/// A `TokenArena` cursor that produces `Token`s. #[derive(Clone, Debug)] pub struct TokenCursor { - // Cursor for the current (innermost) token stream. The `next_idx` within the - // cursor can point to any token tree in the stream (or one past the end). - // The delimiters for this token stream are found in the current token tree - // in `self.stack.last()`; if that is `None` we are in the outermost token - // stream which never has delimiters. - curr: TokenTreeCursor, - - // Token streams surrounding the current one. The `next_idx` within each cursor - // is always greater than zero and always points one past the current - // `TokenTree::Delimited`. - stack: Vec, + pub arena: Arc, + /// Global index into the token arena. + index: usize, + /// The current delimited sequences that we are inside of. + stack: Vec<(DelimitedBounds, DelimitedData)>, } impl TokenCursor { #[inline] pub fn new(arena: TokenArena) -> Self { - let stream = arena.to_token_stream(); - TokenCursor { curr: TokenTreeCursor::new(stream), stack: vec![] } + TokenCursor { arena: Arc::new(arena), index: 0, stack: vec![] } } /// Gets the next token and advances the cursor by one. @@ -981,30 +923,61 @@ impl TokenCursor { } /// An `n` of 1 is the next token tree in the current token stream; won't look outside the - /// current token stream. `look_ahead(0)` isn't allowed and will panic. + /// current delimited sequence. `look_ahead(0)` isn't allowed and will panic. #[inline] - pub fn look_ahead(&self, n: usize) -> Option<&TokenTree> { - self.curr.look_ahead(n) + pub fn look_ahead(&self, n: usize) -> Option<&ArenaTokenTree> { + assert_ne!(n, 0); + let mut index = self.index; + for _ in 0..n.saturating_sub(1) { + let elem = self.arena.get_innermost_elem_at(index); + match elem { + Some(ArenaTokenTree::Token(..)) => { + index += 1; + } + Some(ArenaTokenTree::DelimitedStart(bounds, ..)) => { + // Skip the whole delimited sequence + index = bounds.index_of_next_token_tree(); + } + Some(ArenaTokenTree::DelimitedEnd) => { + // We reached the end of the current delimited sequence + return None; + } + None => { + // We reached the end of the arena + return None; + } + } + } + match self.arena.get_innermost_elem_at(index) { + None | Some(ArenaTokenTree::DelimitedEnd) => None, + Some(t) => Some(t), + } } /// Returns the first token tree (if there is one) past the close delimiter of the enclosing /// delimited sequence. Panics if we are not within a delimited sequence. #[inline] - pub fn look_ahead_past_close_delim(&self) -> Option<&TokenTree> { - self.stack.last().unwrap().next() + pub fn look_ahead_past_close_delim(&self) -> Option<&ArenaTokenTree> { + let (bounds, _) = self.stack.last().unwrap(); + self.arena.get_innermost_elem_at(bounds.index_of_next_token_tree()) } /// Clones the `TokenTree::Delimited` that we are currently within. Panics if we are not within /// a delimited sequence. #[inline] - pub fn clone_enclosing_delim(&self) -> TokenTree { - self.stack.last().unwrap().curr().unwrap().clone() + pub fn clone_enclosing_delim(&self) -> ArenaTokenTree { + let &(bounds, data) = self.stack.last().unwrap(); + ArenaTokenTree::DelimitedStart(bounds, data) } /// For skipping to the end of the current sequence, in rare circumstances. #[inline] pub fn bump_to_end(&mut self) { - self.curr.bump_to_end() + if let Some((bounds, _)) = self.stack.last() { + self.index = bounds.index_of_closing_delimiter(); + } else { + self.index = self.arena.length(); + } } /// Note: the outermost stream has depth of 0. @@ -1016,10 +989,8 @@ impl TokenCursor { /// Returns details about the parent delimited sequence, if there is one. #[inline] pub fn parent_delim_and_span(&self) -> Option<(Delimiter, DelimSpan)> { - if let Some(last) = self.stack.last() - && let Some(TokenTree::Delimited(span, _, delim, _)) = last.curr() - { - Some((*delim, *span)) + if let Some((_, data)) = self.stack.last() { + Some((data.delimiter, data.span)) } else { None } @@ -1032,35 +1003,47 @@ impl TokenCursor { // FIXME: we currently don't return `Delimiter::Invisible` open/close delims. To fix // #67062 we will need to, whereupon the `delim != Delimiter::Invisible` conditions // below can be removed. - if let Some(tree) = self.curr.next() { + if let Some(tree) = self.arena.get_innermost_elem_at(self.index) { match tree { - &TokenTree::Token(token, spacing) => { + &ArenaTokenTree::Token(token, spacing) => { debug_assert!(!token.kind.is_delim()); - let res = (token, spacing); - self.curr.bump(); - return res; + self.index += 1; + return (token, spacing); } - &TokenTree::Delimited(sp, spacing, delim, ref tts) => { - let trees = TokenTreeCursor::new(tts.clone()); - self.curr.bump(); // move past the `Delimited` - self.stack.push(mem::replace(&mut self.curr, trees)); - if !delim.skip() { - return (Token::new(delim.as_open_token_kind(), sp.open), spacing.open); + &ArenaTokenTree::DelimitedStart(bounds, data) => { + self.stack.push((bounds, data)); + self.index += 1; + if !data.delimiter.skip() { + return ( + Token::new(data.delimiter.as_open_token_kind(), data.span.open), + data.spacing.open, + ); } // No open delimiter to return; continue on to the next iteration. } - }; - } else if let Some(parent) = self.stack.pop() { - // We have exhausted this token stream. Move back to its parent token stream. - let Some(&TokenTree::Delimited(span, spacing, delim, _)) = parent.curr() else { - panic!("parent should be Delimited") - }; - self.curr = parent; - if !delim.skip() { - return (Token::new(delim.as_close_token_kind(), span.close), spacing.close); + &ArenaTokenTree::DelimitedEnd => { + // Pop the stack + self.index += 1; + let (_, data) = self.stack.pop().unwrap(); + if !data.delimiter.skip() { + return ( + Token::new(data.delimiter.as_close_token_kind(), data.span.close), + data.spacing.close, + ); + } + } } - // No close delimiter to return; continue on to the next iteration. } else { + // self.index += 1; + // let (_, data) = self.stack.pop().unwrap(); + // if !data.delimiter.skip() { + // return ( + // Token::new(data.delimiter.as_close_token_kind(), data.span.close), + // data.spacing.close, + // ); + // } + assert!(self.stack.is_empty()); + // We have exhausted the outermost token stream. The use of // `Spacing::Alone` is arbitrary and immaterial, because the // `Eof` token's spacing is never used. diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index d6c4e1a1fc207..c6c2186258ac0 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -97,7 +97,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // A brace-delimited block whose first token is `&&`/`||` usually means // the user meant to continue an if-let chain, e.g. `if let P = e { && cond {`. if Delimiter::Brace == open_delim - && let Some(ArenaTokenTree::Token(tok, _)) = arena.get_item_at(index) + && let Some(ArenaTokenTree::Token(tok, _)) = arena.get_innermost_elem_at(index) && matches!(tok.kind, token::AndAnd | token::OrOr) { self.diag_info.if_let_chain_hint_spans.push(tok.span); diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 57fe19226066c..570980d8f53ca 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -2,7 +2,7 @@ use ast::token::IdentIsRaw; use rustc_ast as ast; use rustc_ast::ast::*; use rustc_ast::token::{self, InvisibleOrigin, MetaVarKind, TokenKind}; -use rustc_ast::tokenstream::TokenTree; +use rustc_ast::tokenarena::ArenaTokenTree; use rustc_ast::util::case::Case; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, PResult}; @@ -356,8 +356,9 @@ impl<'a> Parser<'a> { && self.look_ahead(1, |t| t.can_begin_string_literal()) && (self.tree_look_ahead(2, |tt| { match tt { - TokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), - TokenTree::Delimited(..) => false, + ArenaTokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), + ArenaTokenTree::DelimitedStart(..) => false, + _ => unreachable!() } }) == Some(true) || // This branch is only for better diagnostics; `pub`, `unsafe`, etc. are not @@ -365,17 +366,19 @@ impl<'a> Parser<'a> { (self.may_recover() && self.tree_look_ahead(2, |tt| { match tt { - TokenTree::Token(t, _) => + ArenaTokenTree::Token(t, _) => ALL_QUALS.iter().any(|exp| { t.is_keyword(exp.kw) }), - TokenTree::Delimited(..) => false, + ArenaTokenTree::DelimitedStart(..) => false, + _ => unreachable!() } }) == Some(true) && self.tree_look_ahead(3, |tt| { match tt { - TokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), - TokenTree::Delimited(..) => false, + ArenaTokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), + ArenaTokenTree::DelimitedStart(..) => false, + _ => unreachable!() } }) == Some(true) ) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index b252a378722f3..6109b6429bce3 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -5,6 +5,7 @@ use ast::token::IdentIsRaw; use rustc_ast as ast; use rustc_ast::ast::*; use rustc_ast::token::{self, Delimiter, MetaVarKind, TokenKind}; +use rustc_ast::tokenarena::ArenaTokenTree; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::util::case::Case; use rustc_ast_pretty::pprust; @@ -1116,7 +1117,7 @@ impl<'a> Parser<'a> { SUFFIXES.iter().any(|suffix| { suffix.iter().enumerate().all(|(i, kw)| { self.tree_look_ahead(i + 2, |t| { - if let TokenTree::Token(token, _) = t { + if let ArenaTokenTree::Token(token, _) = t { token.is_keyword(*kw) } else { false @@ -1661,7 +1662,7 @@ impl<'a> Parser<'a> { // might be a metavariable i.e. an invisible-delimited sequence, and // `tree_look_ahead` will consider that a single element when looking // ahead. - self.tree_look_ahead(n, |t| matches!(t, TokenTree::Delimited(_, _, Delimiter::Brace, _))) + self.tree_look_ahead(n, |t| matches!(t, ArenaTokenTree::DelimitedStart(_, data) if matches!(data.delimiter, Delimiter::Brace))) == Some(true) } diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index e6d4d177fbb9c..670a1c23922a0 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -29,7 +29,7 @@ pub use path::PathStyle; use rustc_ast::token::{ self, IdentIsRaw, InvisibleOrigin, MetaVarKind, NtExprKind, NtPatKind, Token, TokenKind, }; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::{ArenaTokenTree, TokenArena}; use rustc_ast::tokenstream::{ ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, WithTokens, }; @@ -504,7 +504,7 @@ impl<'a> Parser<'a> { 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 + Some(ArenaTokenTree::Token(token::Token { kind, .. }, _)) if kind == tok ) } @@ -1157,12 +1157,16 @@ impl<'a> Parser<'a> { 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)); + ArenaTokenTree::Token(token, _) => return looker(token), + &ArenaTokenTree::DelimitedStart(_, data) => { + if !data.delimiter.skip() { + return looker(&Token::new( + data.delimiter.as_open_token_kind(), + data.span.open, + )); } } + _ => unreachable!(), } } None => { @@ -1201,7 +1205,7 @@ impl<'a> Parser<'a> { pub fn tree_look_ahead( &self, dist: usize, - looker: impl FnOnce(&TokenTree) -> R, + looker: impl FnOnce(&ArenaTokenTree) -> R, ) -> Option { self.token_cursor.look_ahead(dist).map(looker) } @@ -1390,6 +1394,7 @@ impl<'a> Parser<'a> { // 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(); + let tree = tree.to_token_tree(&self.token_cursor.arena); debug_assert_matches!(tree, TokenTree::Delimited(..)); // Advance the token cursor through the entire delimited From 371bc3f770a540f92bd1c47f71b966bf47a40f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 14:17:10 +0200 Subject: [PATCH 05/25] Push arena token trees further down the stack --- compiler/rustc_ast/src/tokenarena.rs | 30 +++++++++++ compiler/rustc_expand/src/mbe/macro_rules.rs | 52 ++++++++++++------- compiler/rustc_parse/src/parser/cfg_select.rs | 12 +++-- compiler/rustc_parse/src/parser/item.rs | 6 ++- compiler/rustc_parse/src/parser/mod.rs | 21 +++++--- .../rustc_parse/src/parser/nonterminal.rs | 4 +- 6 files changed, 95 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 02c0a905ef397..afbe9289cfc92 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,5 +1,6 @@ use rustc_index::static_assert_size; use rustc_macros::{Decodable, Encodable, StableHash}; +use rustc_span::Span; use crate::token::{Delimiter, Token}; use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; @@ -28,6 +29,31 @@ impl ArenaTokenTree { ArenaTokenTree::DelimitedEnd => unreachable!(), } } + + /// Retrieves the `TokenTree`'s span. + pub fn span(&self) -> Span { + match self { + Self::Token(token, _) => token.span, + Self::DelimitedStart(_, data) => data.span.entire(), + _ => unreachable!(), + } + } + + pub fn to_delimited_data(&self) -> Option<&DelimitedData> { + match self { + ArenaTokenTree::Token(_, _) => None, + ArenaTokenTree::DelimitedStart(_, data) => Some(data), + ArenaTokenTree::DelimitedEnd => unreachable!(), + } + } + + pub fn to_delimited_bounds(&self) -> Option<&DelimitedBounds> { + match self { + ArenaTokenTree::Token(_, _) => None, + ArenaTokenTree::DelimitedStart(bounds, _) => Some(bounds), + ArenaTokenTree::DelimitedEnd => unreachable!(), + } + } } static_assert_size!(ArenaTokenTree, 36); @@ -186,6 +212,10 @@ impl DelimitedBounds { pub fn index_of_closing_delimiter(&self) -> usize { self.index_of_next_token_tree().saturating_sub(1) } + + pub fn is_empty(&self) -> bool { + self.length == 2 + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index d8018ec15d392..a1f8624deebc2 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -7,8 +7,8 @@ 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::tokenarena::TokenArena; -use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; +use rustc_ast::tokenarena::{DelimitedBounds, DelimitedData, TokenArena}; +use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId, Safety}; use rustc_ast_pretty::pprust; use rustc_attr_ir::diagnostic::Directive; @@ -823,9 +823,17 @@ pub fn compile_declarative_macro( if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") { return dummy_syn_ext(guar); } - let args = p.parse_token_tree(); - check_args_parens(sess, sym::attr, &args); - let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition); + let tt = p.parse_token_tree(); + let args = tt.to_delimited_data(); + check_args_parens(sess, sym::attr, args); + let args = parse_one_tt( + tt.to_token_tree(p.arena()), + RulePart::Pattern, + sess, + node_id, + features, + edition, + ); check_emission(check_lhs(sess, features, node_id, &args)); if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") { return dummy_syn_ext(guar); @@ -845,9 +853,10 @@ pub fn compile_declarative_macro( if let Some(guar) = check_no_eof(sess, &p, "expected `()` after `derive`") { return dummy_syn_ext(guar); } - let args = p.parse_token_tree(); - check_args_parens(sess, sym::derive, &args); - let args_empty_result = check_args_empty(sess, &args); + let tt = p.parse_token_tree(); + let args = tt.to_delimited_data(); + check_args_parens(sess, sym::derive, args); + let args_empty_result = check_args_empty(sess, tt.to_delimited_bounds(), tt.span()); let args_not_empty = args_empty_result.is_err(); check_emission(args_empty_result); if let Some(guar) = check_no_eof(sess, &p, "expected macro derive body") { @@ -873,7 +882,7 @@ pub fn compile_declarative_macro( } (None, false) }; - let lhs_tt = p.parse_token_tree(); + let lhs_tt = p.parse_token_tree().to_token_tree(p.arena()); let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition); check_emission(check_lhs(sess, features, node_id, &lhs_tt)); if let Err(e) = p.expect(exp!(FatArrow)) { @@ -882,7 +891,7 @@ pub fn compile_declarative_macro( if let Some(guar) = check_no_eof(sess, &p, "expected right-hand side of macro rule") { return dummy_syn_ext(guar); } - let rhs = p.parse_token_tree(); + let rhs = p.parse_token_tree().to_token_tree(p.arena()); let rhs = parse_one_tt(rhs, RulePart::Body, sess, node_id, features, edition); check_emission(check_rhs(sess, &rhs)); check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs)); @@ -962,25 +971,32 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option) { // This does not handle the non-delimited case; that gets handled separately by `check_lhs`. - if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args - && *delim != Delimiter::Parenthesis + if let Some(data) = args + && data.delimiter != Delimiter::Parenthesis { sess.dcx().emit_err(diagnostics::MacroArgsBadDelim { - span: dspan.entire(), - sugg: diagnostics::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close }, + span: data.span.entire(), + sugg: diagnostics::MacroArgsBadDelimSugg { + open: data.span.open, + close: data.span.close, + }, rule_kw, }); } } -fn check_args_empty(sess: &Session, args: &tokenstream::TokenTree) -> Result<(), ErrorGuaranteed> { +fn check_args_empty( + sess: &Session, + args: Option<&DelimitedBounds>, + span: Span, +) -> Result<(), ErrorGuaranteed> { match args { - tokenstream::TokenTree::Delimited(.., delimited) if delimited.is_empty() => Ok(()), + Some(bounds) if bounds.is_empty() => Ok(()), _ => { let msg = "`derive` rules do not accept arguments; `derive` must be followed by `()`"; - Err(sess.dcx().span_err(args.span(), msg)) + Err(sess.dcx().span_err(span, msg)) } } } diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index cf1ef62e56d5a..df754d772ea44 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,4 +1,4 @@ -use rustc_ast::token; +use rustc_ast::tokenarena::ArenaTokenTree; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; use rustc_errors::PResult; @@ -20,12 +20,16 @@ impl<'a> Parser<'a> { if self.token == token::OpenBrace { // Strip the outer '{' and '}'. match self.parse_token_tree() { - TokenTree::Token(..) => unreachable!("because the current token is a '{{'"), - TokenTree::Delimited(.., tts) => { + ArenaTokenTree::Token(..) => unreachable!("because the current token is a '{{'"), + tree @ ArenaTokenTree::DelimitedStart(..) => { // Optionally end with a comma. let _ = self.eat(exp!(Comma)); - return Ok(tts); + return Ok(match tree.to_token_tree(&self.token_cursor.arena) { + TokenTree::Token(_, _) => unreachable!(), + TokenTree::Delimited(_, _, _, tts) => tts, + }); } + _ => unreachable!(), } } let attrs = AttrWrapper::empty(); // FIXME expressions with attributes can be supported here diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 6109b6429bce3..5cd4dbd09cea7 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2603,7 +2603,11 @@ impl<'a> Parser<'a> { // Convert `MacParams MacBody` into `{ MacParams => MacBody }`. let bspan = body.span(); let arrow = TokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` - let tokens = TokenStream::new(vec![params, arrow, body]); + let tokens = TokenStream::new(vec![ + params.to_token_tree(&self.token_cursor.arena), + arrow, + body.to_token_tree(&self.token_cursor.arena), + ]); let dspan = DelimSpan::from_pair(pspan.shrink_to_lo(), bspan.shrink_to_hi()); Box::new(DelimArgs { dspan, delim: Delimiter::Brace, tokens }) } else { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 670a1c23922a0..46ea57215a849 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -244,6 +244,12 @@ pub struct Parser<'a> { pub fn_body_missing_semi_guar: Option = None, } +impl<'a> Parser<'a> { + pub fn arena(&self) -> &TokenArena { + &self.token_cursor.arena + } +} + // 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. @@ -1381,7 +1387,9 @@ impl<'a> Parser<'a> { || self.check(exp!(OpenBrace)); delimited.then(|| { - let TokenTree::Delimited(dspan, _, delim, tokens) = self.parse_token_tree() else { + let TokenTree::Delimited(dspan, _, delim, tokens) = + self.parse_token_tree().to_token_tree(&self.token_cursor.arena) + else { unreachable!() }; DelimArgs { dspan, delim, tokens } @@ -1389,13 +1397,12 @@ impl<'a> Parser<'a> { } /// Parses a single token tree from the input. - pub fn parse_token_tree(&mut self) -> TokenTree { + pub fn parse_token_tree(&mut self) -> ArenaTokenTree { 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(); - let tree = tree.to_token_tree(&self.token_cursor.arena); - debug_assert_matches!(tree, TokenTree::Delimited(..)); + debug_assert_matches!(tree, ArenaTokenTree::DelimitedStart(..)); // Advance the token cursor through the entire delimited // sequence. After getting the `OpenDelim` we are *within* the @@ -1431,7 +1438,7 @@ impl<'a> Parser<'a> { assert!(!self.token.kind.is_close_delim_or_eof()); let prev_spacing = self.token_spacing; self.bump(); - TokenTree::Token(self.prev_token, prev_spacing) + ArenaTokenTree::Token(self.prev_token, prev_spacing) } } @@ -1444,7 +1451,9 @@ impl<'a> Parser<'a> { result.push(self.parse_token_tree()); } } - TokenStream::new(result) + TokenStream::new( + result.into_iter().map(|tt| tt.to_token_tree(&self.token_cursor.arena)).collect(), + ) } /// Evaluates the closure with restrictions in place. diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 9f9545c194082..67f8fd0964b34 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -125,7 +125,9 @@ 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().to_token_tree(&self.token_cursor.arena), + )), NonterminalKind::Item => match self .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? { From 5a32c28acf7a4b0d9f5b69bd151710fe21daf9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 14:30:31 +0200 Subject: [PATCH 06/25] Pass `&mut TokenArena` to `lex_token_trees` --- compiler/rustc_parse/src/lexer/mod.rs | 8 ++++---- compiler/rustc_parse/src/lib.rs | 23 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 7cac44aff9d51..e1a0cf3f996e6 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -67,9 +67,10 @@ pub(crate) fn lex_token_trees<'psess, 'src>( psess: &'psess ParseSess, mut src: &'src str, mut start_pos: BytePos, + arena: &mut TokenArena, override_span: Option, strip_tokens: StripTokens, -) -> Result>> { +) -> Result<(), Vec>> { match strip_tokens { StripTokens::Shebang | StripTokens::ShebangAndFrontmatter => { if let Some(shebang_len) = rustc_lexer::strip_shebang(src) { @@ -98,8 +99,7 @@ pub(crate) fn lex_token_trees<'psess, 'src>( token: Token::dummy(), diag_info: TokenTreeDiagInfo::default(), }; - let mut arena = TokenArena::new(Vec::new()); - let res = lexer.lex_token_trees(&mut arena, /* is_delimited */ false); + let res = lexer.lex_token_trees(arena, /* is_delimited */ false); let mut unmatched_closing_delims: Vec<_> = make_errors_for_mismatched_closing_delims(&lexer.diag_info.unmatched_delims, psess); @@ -107,7 +107,7 @@ pub(crate) fn lex_token_trees<'psess, 'src>( match res { Ok(_) => { if unmatched_closing_delims.is_empty() { - Ok(arena) + Ok(()) } else { // Return error if there are unmatched delimiters or unclosed delimiters. Err(unmatched_closing_delims) diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 1f0416f8dc102..f5ee5fdb96df7 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -271,7 +271,16 @@ fn source_file_to_stream<'psess>( )); }); - lexer::lex_token_trees(psess, src.as_str(), source_file.start_pos, override_span, strip_tokens) + let mut arena = TokenArena::default(); + lexer::lex_token_trees( + psess, + src.as_str(), + source_file.start_pos, + &mut arena, + override_span, + strip_tokens, + )?; + Ok(arena) } /// Runs the given subparser `f` on the tokens of the given `attr`'s item. @@ -350,8 +359,16 @@ fn lex_token_trees_for_span( span: 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(arena) => arena.to_token_stream(), + let mut arena = TokenArena::default(); + let stream = match lexer::lex_token_trees( + psess, + &src, + span.lo(), + &mut arena, + None, + StripTokens::Nothing, + ) { + Ok(_) => arena.to_token_stream(), Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); return None; From 032b22c353bce1f9e325e7e7586239a3a4a01e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 14:39:21 +0200 Subject: [PATCH 07/25] Migrate `fake_token_stream_for_file_mod` to `TokenArena` --- compiler/rustc_ast/src/attr/mod.rs | 23 ++++++ compiler/rustc_ast/src/tokenarena.rs | 53 ++++++++------ compiler/rustc_parse/src/lib.rs | 71 ++++++++----------- compiler/rustc_parse/src/parser/cfg_select.rs | 1 + src/librustdoc/clean/render_macro_matchers.rs | 6 +- 5 files changed, 93 insertions(+), 61 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 40a1b4bd32218..59d60c3268bf6 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -19,6 +19,7 @@ use crate::ast::{ use crate::token::{ self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, }; +use crate::tokenarena::{ArenaTokenTree, TokenArena}; use crate::tokenstream::{ AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, @@ -308,6 +309,28 @@ impl Attribute { } } + pub fn push_token_trees(&self, arena: &mut TokenArena) { + match self.kind { + AttrKind::Normal(ref normal) => { + for token_tree in normal + .tokens + .as_ref() + .unwrap_or_else(|| panic!("attribute is missing tokens: {self:?}")) + .to_attr_token_stream() + .to_token_trees() + { + arena.push_token_tree(&token_tree); + } + } + // Empty tokens here ensures synthetic attributes are invisible to proc macros. + AttrKind::Synthetic(..) => {} + AttrKind::DocComment(comment_kind, data) => arena.push(ArenaTokenTree::token_alone( + token::DocComment(comment_kind, self.style, data), + self.span, + )), + } + } + pub fn deprecation_note(&self) -> Option { match &self.kind { AttrKind::Normal(normal) if normal.item.path == sym::deprecated => { diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index afbe9289cfc92..b17c35d67931f 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -2,7 +2,7 @@ use rustc_index::static_assert_size; use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Span; -use crate::token::{Delimiter, Token}; +use crate::token::{Delimiter, Token, TokenKind}; use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; /// Part of a `TokenArena`. @@ -13,11 +13,16 @@ pub enum ArenaTokenTree { Token(Token, Spacing), /// A delimited sequence of token trees. DelimitedStart(DelimitedBounds, DelimitedData), - // TODO: get rid of this and represent it implicitly + // FIXME: get rid of this and represent it implicitly DelimitedEnd, } impl ArenaTokenTree { + /// Create a `TokenTree::Token` with alone spacing. + pub fn token_alone(kind: TokenKind, span: Span) -> ArenaTokenTree { + ArenaTokenTree::Token(Token::new(kind, span), Spacing::Alone) + } + /// Convert an arena token tree to the tree-shaped token tree. pub fn to_token_tree(&self, arena: &TokenArena) -> TokenTree { match self { @@ -64,14 +69,18 @@ pub struct TokenArena { } impl TokenArena { - pub fn new(tokens: Vec) -> Self { - Self { tokens } - } - pub fn push(&mut self, token: ArenaTokenTree) { self.tokens.push(token); } + pub fn pop(&mut self) -> Option { + let tree = self.tokens.pop(); + if let Some(tree) = &tree { + assert!(matches!(tree, ArenaTokenTree::Token(..))); + } + tree + } + /// Iter top-level token trees of a delimited token sequence. pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { let mut index = (bounds.start + 1) as usize; @@ -163,24 +172,28 @@ impl TokenArena { arena } - fn fill(&mut self, stream: &TokenStream) { - for item in stream.iter() { - match item { - TokenTree::Token(token, spacing) => { - self.tokens.push(ArenaTokenTree::Token(*token, *spacing)); - } - TokenTree::Delimited(span, spacing, delimiter, stream) => { - let start = self.start_delimited(); - self.fill(stream); - self.finish_delimited( - start, - DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, - ); - } + pub fn push_token_tree(&mut self, tt: &TokenTree) { + match tt { + TokenTree::Token(token, spacing) => { + self.tokens.push(ArenaTokenTree::Token(*token, *spacing)); + } + TokenTree::Delimited(span, spacing, delimiter, stream) => { + let start = self.start_delimited(); + self.fill(stream); + self.finish_delimited( + start, + DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, + ); } } } + fn fill(&mut self, stream: &TokenStream) { + for tt in stream.iter() { + self.push_token_tree(tt); + } + } + pub fn to_token_stream(&self) -> TokenStream { let mut tokens = vec![]; for tt in self.iter_top_level_trees() { diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index f5ee5fdb96df7..dcafa9fd111bc 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -15,7 +15,7 @@ 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, Spacing}; use rustc_ast_pretty::pprust; use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize}; pub use rustc_lexer::UNICODE_VERSION; @@ -29,7 +29,7 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); #[macro_use] pub mod parser; use parser::Parser; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::{ArenaTokenTree, DelimitedData, TokenArena}; use crate::lexer::StripTokens; @@ -303,8 +303,8 @@ pub fn fake_token_stream_for_item( item: &ast::Item, attr_to_exclude: Option<&ast::Attribute>, ) -> TokenArena { - if let Some(tokens) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { - return TokenArena::from_stream(&tokens); + if let Some(arena) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { + return arena; } let source = pprust::item_to_string(item); @@ -316,7 +316,7 @@ fn fake_token_stream_for_file_mod( psess: &ParseSess, item: &ast::Item, attr_to_exclude: Option<&ast::Attribute>, -) -> Option { +) -> Option { let ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(_, ast::Inline::No { .. }, spans)) = &item.kind else { @@ -326,55 +326,46 @@ fn fake_token_stream_for_file_mod( let attr = attr_to_exclude.expect("file modules must have an attribute to exclude"); assert_eq!(attr.style, ast::AttrStyle::Inner); - let mut body_tts = Vec::new(); - body_tts.extend(lex_token_trees_for_span(psess, spans.inner_span.until(attr.span))?); - body_tts.extend(lex_token_trees_for_span( - psess, - attr.span.between(spans.inner_span.shrink_to_hi()), - )?); + let mut arena = TokenArena::default(); - let mut wrapper_tts = Vec::new(); for attr in item.attrs.iter().filter(|attr| attr.style == ast::AttrStyle::Outer) { - wrapper_tts.extend(attr.token_trees()); + attr.push_token_trees(&mut arena); } - wrapper_tts.extend(lex_token_trees_for_span(psess, item.span)?); - let Some(TokenTree::Token(semi, _)) = wrapper_tts.pop() else { + lex_token_trees_for_span(psess, item.span, &mut arena)?; + let Some(ArenaTokenTree::Token(semi, _)) = arena.pop() else { return None; }; if semi.kind != token::Semi { return None; } - wrapper_tts.push(TokenTree::Delimited( - DelimSpan::from_single(semi.span), - DelimSpacing::new(Spacing::Alone, Spacing::Alone), - token::Delimiter::Brace, - TokenStream::new(body_tts), - )); - - Some(TokenStream::new(wrapper_tts)) -} -fn lex_token_trees_for_span( - psess: &ParseSess, - span: Span, -) -> Option> { - let src = psess.source_map().span_to_snippet(span).ok()?; - let mut arena = TokenArena::default(); - let stream = match lexer::lex_token_trees( + let start = arena.start_delimited(); + lex_token_trees_for_span(psess, spans.inner_span.until(attr.span), &mut arena)?; + lex_token_trees_for_span( psess, - &src, - span.lo(), + attr.span.between(spans.inner_span.shrink_to_hi()), &mut arena, - None, - StripTokens::Nothing, - ) { - Ok(_) => arena.to_token_stream(), + )?; + arena.finish_delimited( + start, + DelimitedData { + span: DelimSpan::from_single(semi.span), + spacing: DelimSpacing::new(Spacing::Alone, Spacing::Alone), + delimiter: token::Delimiter::Brace, + }, + ); + Some(arena) +} + +fn lex_token_trees_for_span(psess: &ParseSess, span: Span, arena: &mut TokenArena) -> Option<()> { + let src = psess.source_map().span_to_snippet(span).ok()?; + match lexer::lex_token_trees(psess, &src, span.lo(), arena, None, StripTokens::Nothing) { + Ok(_) => Some(()), Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); - return None; + None } - }; - Some((0..).map_while(move |index| stream.get(index).cloned())) + } } pub fn fake_token_stream_for_foreign_item( diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index df754d772ea44..01460d7ae114c 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,3 +1,4 @@ +use rustc_ast::token; use rustc_ast::tokenarena::ArenaTokenTree; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs index a69e3808bd7f7..70498dd3ef090 100644 --- a/src/librustdoc/clean/render_macro_matchers.rs +++ b/src/librustdoc/clean/render_macro_matchers.rs @@ -88,7 +88,11 @@ fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option, tt: &TokenTree) { From 42d912541bed8bc604578ff8565b06740c5969dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 09:11:54 +0200 Subject: [PATCH 08/25] Ensure that we only push tokens directly to the arena --- compiler/rustc_ast/src/attr/mod.rs | 4 ++-- compiler/rustc_ast/src/tokenarena.rs | 8 ++++++-- compiler/rustc_parse/src/lexer/tokentrees.rs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 59d60c3268bf6..15b4d98f0b7ff 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -19,7 +19,7 @@ use crate::ast::{ use crate::token::{ self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, }; -use crate::tokenarena::{ArenaTokenTree, TokenArena}; +use crate::tokenarena::TokenArena; use crate::tokenstream::{ AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, @@ -324,7 +324,7 @@ impl Attribute { } // Empty tokens here ensures synthetic attributes are invisible to proc macros. AttrKind::Synthetic(..) => {} - AttrKind::DocComment(comment_kind, data) => arena.push(ArenaTokenTree::token_alone( + AttrKind::DocComment(comment_kind, data) => arena.push_token_alone(Token::new( token::DocComment(comment_kind, self.style, data), self.span, )), diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index b17c35d67931f..8ba9af3adbe11 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -69,8 +69,12 @@ pub struct TokenArena { } impl TokenArena { - pub fn push(&mut self, token: ArenaTokenTree) { - self.tokens.push(token); + pub fn push_token(&mut self, token: Token, spacing: Spacing) { + self.tokens.push(ArenaTokenTree::Token(token, spacing)); + } + + pub fn push_token_alone(&mut self, token: Token) { + self.tokens.push(ArenaTokenTree::Token(token, Spacing::Alone)); } pub fn pop(&mut self) -> Option { diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index c6c2186258ac0..e309c719f4260 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -45,7 +45,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { } else { // Get the next normal token. let (this_tok, this_spacing) = self.bump(); - arena.push(ArenaTokenTree::Token(this_tok, this_spacing)); + arena.push_token(this_tok, this_spacing); } } } From e751e41fc8f654967febbe8ecf8647884deca6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 09:52:34 +0200 Subject: [PATCH 09/25] Represent closing delimiters implicitly --- compiler/rustc_ast/src/tokenarena.rs | 22 ++----- compiler/rustc_ast/src/tokenstream.rs | 61 ++++++++++--------- compiler/rustc_parse/src/parser/cfg_select.rs | 1 - compiler/rustc_parse/src/parser/function.rs | 3 - compiler/rustc_parse/src/parser/mod.rs | 3 +- 5 files changed, 37 insertions(+), 53 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 8ba9af3adbe11..0f2f9e6a16d95 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -13,8 +13,6 @@ pub enum ArenaTokenTree { Token(Token, Spacing), /// A delimited sequence of token trees. DelimitedStart(DelimitedBounds, DelimitedData), - // FIXME: get rid of this and represent it implicitly - DelimitedEnd, } impl ArenaTokenTree { @@ -31,7 +29,6 @@ impl ArenaTokenTree { let tts = arena.iter_delimited(bounds).map(|tt| tt.to_token_tree(arena)).collect(); TokenTree::Delimited(data.span, data.spacing, data.delimiter, TokenStream::new(tts)) } - ArenaTokenTree::DelimitedEnd => unreachable!(), } } @@ -40,7 +37,6 @@ impl ArenaTokenTree { match self { Self::Token(token, _) => token.span, Self::DelimitedStart(_, data) => data.span.entire(), - _ => unreachable!(), } } @@ -48,7 +44,6 @@ impl ArenaTokenTree { match self { ArenaTokenTree::Token(_, _) => None, ArenaTokenTree::DelimitedStart(_, data) => Some(data), - ArenaTokenTree::DelimitedEnd => unreachable!(), } } @@ -56,7 +51,6 @@ impl ArenaTokenTree { match self { ArenaTokenTree::Token(_, _) => None, ArenaTokenTree::DelimitedStart(bounds, _) => Some(bounds), - ArenaTokenTree::DelimitedEnd => unreachable!(), } } } @@ -88,7 +82,7 @@ impl TokenArena { /// Iter top-level token trees of a delimited token sequence. pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { let mut index = (bounds.start + 1) as usize; - let end = bounds.index_of_next_token_tree().saturating_sub(1); + let end = bounds.index_of_next_token_tree(); std::iter::from_fn(move || { if index >= end { return None; @@ -103,7 +97,6 @@ impl TokenArena { index = bounds.index_of_next_token_tree(); Some(*tree) } - ArenaTokenTree::DelimitedEnd => unreachable!(), } }) } @@ -125,7 +118,6 @@ impl TokenArena { index = bounds.index_of_next_token_tree(); Some(*tree) } - ArenaTokenTree::DelimitedEnd => unreachable!(), } }) } @@ -144,11 +136,10 @@ impl TokenArena { } pub fn finish_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { - self.tokens.push(ArenaTokenTree::DelimitedEnd); let length = self.length(); match &mut self.tokens[open.start] { - tree @ (ArenaTokenTree::Token(..) | ArenaTokenTree::DelimitedEnd) => { - unreachable!("Called finish_delimited on an invalid tree type {tree:?}") + ArenaTokenTree::Token(..) => { + unreachable!("Called finish_delimited on a token"); } ArenaTokenTree::DelimitedStart(bounds, data) => { let len = length.saturating_sub(open.start); @@ -225,13 +216,8 @@ impl DelimitedBounds { (self.start + self.length) as usize } - /// Return the index of the closing delimiter of this token sequence. - pub fn index_of_closing_delimiter(&self) -> usize { - self.index_of_next_token_tree().saturating_sub(1) - } - pub fn is_empty(&self) -> bool { - self.length == 2 + self.length == 1 } } diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 16d9cd9be90c3..513439f7f2d1a 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -907,6 +907,7 @@ pub struct TokenCursor { pub arena: Arc, /// Global index into the token arena. index: usize, + delimited_sequence_end: usize, /// The current delimited sequences that we are inside of. stack: Vec<(DelimitedBounds, DelimitedData)>, } @@ -914,7 +915,8 @@ pub struct TokenCursor { impl TokenCursor { #[inline] pub fn new(arena: TokenArena) -> Self { - TokenCursor { arena: Arc::new(arena), index: 0, stack: vec![] } + let end = arena.length() + 1; + TokenCursor { arena: Arc::new(arena), index: 0, delimited_sequence_end: end, stack: vec![] } } /// Gets the next token and advances the cursor by one. @@ -929,6 +931,9 @@ impl TokenCursor { assert_ne!(n, 0); let mut index = self.index; for _ in 0..n.saturating_sub(1) { + if index == self.delimited_sequence_end { + return None; + } let elem = self.arena.get_innermost_elem_at(index); match elem { Some(ArenaTokenTree::Token(..)) => { @@ -938,19 +943,16 @@ impl TokenCursor { // Skip the whole delimited sequence index = bounds.index_of_next_token_tree(); } - Some(ArenaTokenTree::DelimitedEnd) => { - // We reached the end of the current delimited sequence - return None; - } None => { // We reached the end of the arena return None; } } } - match self.arena.get_innermost_elem_at(index) { - None | Some(ArenaTokenTree::DelimitedEnd) => None, - Some(t) => Some(t), + if index == self.delimited_sequence_end { + None + } else { + self.arena.get_innermost_elem_at(index) } } @@ -974,7 +976,7 @@ impl TokenCursor { #[inline] pub fn bump_to_end(&mut self) { if let Some((bounds, _)) = self.stack.last() { - self.index = bounds.index_of_closing_delimiter(); + self.index = bounds.index_of_next_token_tree(); } else { self.index = self.arena.length(); } @@ -1000,6 +1002,25 @@ impl TokenCursor { #[inline(always)] pub fn inlined_next_and_bump(&mut self) -> (Token, Spacing) { loop { + if self.index == self.delimited_sequence_end { + let (_, data) = self.stack.pop().unwrap(); + + // How much is left for the now-current sequence? + self.delimited_sequence_end = self + .stack + .last() + .map(|(bounds, _)| bounds.index_of_next_token_tree()) + .unwrap_or(self.arena.length() + 1); + + if !data.delimiter.skip() { + return ( + Token::new(data.delimiter.as_close_token_kind(), data.span.close), + data.spacing.close, + ); + } + continue; + } + // FIXME: we currently don't return `Delimiter::Invisible` open/close delims. To fix // #67062 we will need to, whereupon the `delim != Delimiter::Invisible` conditions // below can be removed. @@ -1013,6 +1034,7 @@ impl TokenCursor { &ArenaTokenTree::DelimitedStart(bounds, data) => { self.stack.push((bounds, data)); self.index += 1; + self.delimited_sequence_end = bounds.index_of_next_token_tree(); if !data.delimiter.skip() { return ( Token::new(data.delimiter.as_open_token_kind(), data.span.open), @@ -1021,27 +1043,8 @@ impl TokenCursor { } // No open delimiter to return; continue on to the next iteration. } - &ArenaTokenTree::DelimitedEnd => { - // Pop the stack - self.index += 1; - let (_, data) = self.stack.pop().unwrap(); - if !data.delimiter.skip() { - return ( - Token::new(data.delimiter.as_close_token_kind(), data.span.close), - data.spacing.close, - ); - } - } } } else { - // self.index += 1; - // let (_, data) = self.stack.pop().unwrap(); - // if !data.delimiter.skip() { - // return ( - // Token::new(data.delimiter.as_close_token_kind(), data.span.close), - // data.spacing.close, - // ); - // } assert!(self.stack.is_empty()); // We have exhausted the outermost token stream. The use of @@ -1100,7 +1103,7 @@ mod size_asserts { static_assert_size!(AttrTokenStream, 8); static_assert_size!(AttrTokenTree, 32); static_assert_size!(LazyAttrTokenStream, 8); - static_assert_size!(LazyAttrTokenStreamInner, 88); + static_assert_size!(LazyAttrTokenStreamInner, 96); static_assert_size!(Option, 8); // must be small, used in many AST nodes static_assert_size!(TokenStream, 8); static_assert_size!(TokenTree, 32); diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index 01460d7ae114c..dd4c69953620c 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -30,7 +30,6 @@ impl<'a> Parser<'a> { TokenTree::Delimited(_, _, _, tts) => tts, }); } - _ => unreachable!(), } } let attrs = AttrWrapper::empty(); // FIXME expressions with attributes can be supported here diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 570980d8f53ca..3af3a1931232d 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -358,7 +358,6 @@ impl<'a> Parser<'a> { match tt { ArenaTokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), ArenaTokenTree::DelimitedStart(..) => false, - _ => unreachable!() } }) == Some(true) || // This branch is only for better diagnostics; `pub`, `unsafe`, etc. are not @@ -371,14 +370,12 @@ impl<'a> Parser<'a> { t.is_keyword(exp.kw) }), ArenaTokenTree::DelimitedStart(..) => false, - _ => unreachable!() } }) == Some(true) && self.tree_look_ahead(3, |tt| { match tt { ArenaTokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case), ArenaTokenTree::DelimitedStart(..) => false, - _ => unreachable!() } }) == Some(true) ) diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 46ea57215a849..dfdc8a70d2e71 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -254,7 +254,7 @@ impl<'a> Parser<'a> { // 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); +rustc_data_structures::static_assert_size!(Parser<'_>, 304); /// Stores span information about a closure. #[derive(Clone, Debug)] @@ -1172,7 +1172,6 @@ impl<'a> Parser<'a> { )); } } - _ => unreachable!(), } } None => { From ba4083ccd130fe4b5c2387f9d67c5b81dd8bb372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 10:02:32 +0200 Subject: [PATCH 10/25] Split mutable and immutable part of the arena token stream --- compiler/rustc_ast/src/attr/mod.rs | 4 +- compiler/rustc_ast/src/tokenarena.rs | 157 ++++++++++-------- compiler/rustc_ast/src/tokenstream.rs | 22 +-- compiler/rustc_ast/src/tokenstream/tests.rs | 35 +++- .../rustc_attr_parsing/src/attributes/cfg.rs | 11 +- compiler/rustc_attr_parsing/src/parser.rs | 8 +- .../rustc_attr_parsing/src/validate_attr.rs | 9 +- compiler/rustc_builtin_macros/src/cfg_eval.rs | 4 +- compiler/rustc_expand/src/base.rs | 4 +- compiler/rustc_expand/src/mbe/macro_rules.rs | 14 +- compiler/rustc_expand/src/proc_macro.rs | 4 +- .../rustc_expand/src/proc_macro_server.rs | 4 +- compiler/rustc_parse/src/lexer/mod.rs | 4 +- compiler/rustc_parse/src/lexer/tokentrees.rs | 15 +- compiler/rustc_parse/src/lib.rs | 38 +++-- compiler/rustc_parse/src/parser/cfg_select.rs | 2 +- compiler/rustc_parse/src/parser/item.rs | 4 +- compiler/rustc_parse/src/parser/mod.rs | 14 +- .../rustc_parse/src/parser/nonterminal.rs | 2 +- 19 files changed, 209 insertions(+), 146 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 15b4d98f0b7ff..4086b49ce4c09 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -19,7 +19,7 @@ use crate::ast::{ use crate::token::{ self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, }; -use crate::tokenarena::TokenArena; +use crate::tokenarena::ArenaTokenStreamBuilder; use crate::tokenstream::{ AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, @@ -309,7 +309,7 @@ impl Attribute { } } - pub fn push_token_trees(&self, arena: &mut TokenArena) { + pub fn push_token_trees(&self, arena: &mut ArenaTokenStreamBuilder) { match self.kind { AttrKind::Normal(ref normal) => { for token_tree in normal diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 0f2f9e6a16d95..09bc1f938ca0a 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use rustc_index::static_assert_size; use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Span; @@ -22,7 +24,7 @@ impl ArenaTokenTree { } /// Convert an arena token tree to the tree-shaped token tree. - pub fn to_token_tree(&self, arena: &TokenArena) -> TokenTree { + pub fn to_token_tree(&self, arena: &ArenaTokenStream) -> TokenTree { match self { ArenaTokenTree::Token(token, spacing) => TokenTree::Token(*token, *spacing), ArenaTokenTree::DelimitedStart(bounds, data) => { @@ -57,12 +59,12 @@ impl ArenaTokenTree { static_assert_size!(ArenaTokenTree, 36); -#[derive(Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] -pub struct TokenArena { +#[derive(Debug, Default)] +pub struct ArenaTokenStreamBuilder { tokens: Vec, } -impl TokenArena { +impl ArenaTokenStreamBuilder { pub fn push_token(&mut self, token: Token, spacing: Spacing) { self.tokens.push(ArenaTokenTree::Token(token, spacing)); } @@ -79,47 +81,20 @@ impl TokenArena { tree } - /// Iter top-level token trees of a delimited token sequence. - pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { - let mut index = (bounds.start + 1) as usize; - let end = bounds.index_of_next_token_tree(); - std::iter::from_fn(move || { - if index >= end { - return None; - } - let item = self.get_innermost_elem_at(index)?; - match item { - token @ ArenaTokenTree::Token(..) => { - index += 1; - Some(*token) - } - tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { - index = bounds.index_of_next_token_tree(); - Some(*tree) - } - } - }) - } - - pub fn iter_top_level_trees(&self) -> impl Iterator { - let mut index = 0; - let end = self.tokens.len(); - std::iter::from_fn(move || { - if index >= end { - return None; + pub fn push_token_tree(&mut self, tt: &TokenTree) { + match tt { + TokenTree::Token(token, spacing) => { + self.tokens.push(ArenaTokenTree::Token(*token, *spacing)); } - let item = self.get_innermost_elem_at(index)?; - match item { - token @ ArenaTokenTree::Token(..) => { - index += 1; - Some(*token) - } - tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { - index = bounds.index_of_next_token_tree(); - Some(*tree) - } + TokenTree::Delimited(span, spacing, delimiter, stream) => { + let start = self.start_delimited(); + self.fill(stream); + self.finish_delimited( + start, + DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, + ); } - }) + } } pub fn start_delimited(&mut self) -> OpenDelimited { @@ -153,40 +128,33 @@ impl TokenArena { self.tokens.get(index) } + pub fn finish(self) -> ArenaTokenStream { + ArenaTokenStream { tokens: Arc::new(self.tokens) } + } + pub fn length(&self) -> usize { self.tokens.len() } - pub fn is_empty(&self) -> bool { - self.tokens.is_empty() + fn fill(&mut self, stream: &TokenStream) { + for tt in stream.iter() { + self.push_token_tree(tt); + } } +} - pub fn from_stream(stream: &TokenStream) -> Self { - let mut arena = TokenArena { tokens: Vec::with_capacity(stream.len()) }; - arena.fill(stream); - arena - } +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] +pub struct ArenaTokenStream { + tokens: Arc>, +} - pub fn push_token_tree(&mut self, tt: &TokenTree) { - match tt { - TokenTree::Token(token, spacing) => { - self.tokens.push(ArenaTokenTree::Token(*token, *spacing)); - } - TokenTree::Delimited(span, spacing, delimiter, stream) => { - let start = self.start_delimited(); - self.fill(stream); - self.finish_delimited( - start, - DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, - ); - } - } +impl ArenaTokenStream { + pub fn length(&self) -> usize { + self.tokens.len() } - fn fill(&mut self, stream: &TokenStream) { - for tt in stream.iter() { - self.push_token_tree(tt); - } + pub fn is_empty(&self) -> bool { + self.tokens.is_empty() } pub fn to_token_stream(&self) -> TokenStream { @@ -196,6 +164,59 @@ impl TokenArena { } TokenStream::new(tokens) } + + pub fn get_innermost_elem_at(&self, index: usize) -> Option<&ArenaTokenTree> { + self.tokens.get(index) + } + + pub fn from_stream(stream: &TokenStream) -> Self { + let mut arena = ArenaTokenStreamBuilder { tokens: Vec::with_capacity(stream.len()) }; + arena.fill(stream); + arena.finish() + } + + /// Iter top-level token trees of a delimited token sequence. + pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { + let mut index = (bounds.start + 1) as usize; + let end = bounds.index_of_next_token_tree(); + std::iter::from_fn(move || { + if index >= end { + return None; + } + let item = self.get_innermost_elem_at(index)?; + match item { + token @ ArenaTokenTree::Token(..) => { + index += 1; + Some(*token) + } + tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { + index = bounds.index_of_next_token_tree(); + Some(*tree) + } + } + }) + } + + pub fn iter_top_level_trees(&self) -> impl Iterator { + let mut index = 0; + let end = self.tokens.len(); + std::iter::from_fn(move || { + if index >= end { + return None; + } + let item = self.get_innermost_elem_at(index)?; + match item { + token @ ArenaTokenTree::Token(..) => { + index += 1; + Some(*token) + } + tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { + index = bounds.index_of_next_token_tree(); + Some(*tree) + } + } + }) + } } pub struct OpenDelimited { diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 513439f7f2d1a..986dcfa758b2b 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -20,7 +20,7 @@ use thin_vec::ThinVec; use crate::ast::AttrStyle; use crate::ast_traits::HasTokens; use crate::token::{self, Delimiter, Token, TokenKind}; -use crate::tokenarena::{ArenaTokenTree, DelimitedBounds, DelimitedData, TokenArena}; +use crate::tokenarena::{ArenaTokenStream, ArenaTokenTree, DelimitedBounds, DelimitedData}; use crate::{AttrVec, Attribute}; #[cfg(test)] @@ -904,7 +904,7 @@ impl<'t> Iterator for TokenStreamIter<'t> { /// A `TokenArena` cursor that produces `Token`s. #[derive(Clone, Debug)] pub struct TokenCursor { - pub arena: Arc, + pub stream: ArenaTokenStream, /// Global index into the token arena. index: usize, delimited_sequence_end: usize, @@ -914,9 +914,9 @@ pub struct TokenCursor { impl TokenCursor { #[inline] - pub fn new(arena: TokenArena) -> Self { - let end = arena.length() + 1; - TokenCursor { arena: Arc::new(arena), index: 0, delimited_sequence_end: end, stack: vec![] } + pub fn new(stream: ArenaTokenStream) -> Self { + let end = stream.length() + 1; + TokenCursor { stream, index: 0, delimited_sequence_end: end, stack: vec![] } } /// Gets the next token and advances the cursor by one. @@ -934,7 +934,7 @@ impl TokenCursor { if index == self.delimited_sequence_end { return None; } - let elem = self.arena.get_innermost_elem_at(index); + let elem = self.stream.get_innermost_elem_at(index); match elem { Some(ArenaTokenTree::Token(..)) => { index += 1; @@ -952,7 +952,7 @@ impl TokenCursor { if index == self.delimited_sequence_end { None } else { - self.arena.get_innermost_elem_at(index) + self.stream.get_innermost_elem_at(index) } } @@ -961,7 +961,7 @@ impl TokenCursor { #[inline] pub fn look_ahead_past_close_delim(&self) -> Option<&ArenaTokenTree> { let (bounds, _) = self.stack.last().unwrap(); - self.arena.get_innermost_elem_at(bounds.index_of_next_token_tree()) + self.stream.get_innermost_elem_at(bounds.index_of_next_token_tree()) } /// Clones the `TokenTree::Delimited` that we are currently within. Panics if we are not within @@ -978,7 +978,7 @@ impl TokenCursor { if let Some((bounds, _)) = self.stack.last() { self.index = bounds.index_of_next_token_tree(); } else { - self.index = self.arena.length(); + self.index = self.stream.length(); } } @@ -1010,7 +1010,7 @@ impl TokenCursor { .stack .last() .map(|(bounds, _)| bounds.index_of_next_token_tree()) - .unwrap_or(self.arena.length() + 1); + .unwrap_or(self.stream.length() + 1); if !data.delimiter.skip() { return ( @@ -1024,7 +1024,7 @@ impl TokenCursor { // FIXME: we currently don't return `Delimiter::Invisible` open/close delims. To fix // #67062 we will need to, whereupon the `delim != Delimiter::Invisible` conditions // below can be removed. - if let Some(tree) = self.arena.get_innermost_elem_at(self.index) { + if let Some(tree) = self.stream.get_innermost_elem_at(self.index) { match tree { &ArenaTokenTree::Token(token, spacing) => { debug_assert!(!token.kind.is_delim()); diff --git a/compiler/rustc_ast/src/tokenstream/tests.rs b/compiler/rustc_ast/src/tokenstream/tests.rs index 6c7e82a97c58e..085a0df81007c 100644 --- a/compiler/rustc_ast/src/tokenstream/tests.rs +++ b/compiler/rustc_ast/src/tokenstream/tests.rs @@ -1,7 +1,8 @@ use rustc_span::DUMMY_SP; -use crate::token::TokenKind; -use crate::tokenstream::TokenStream; +use crate::token::{Delimiter, Token, TokenKind}; +use crate::tokenarena::{ArenaTokenStreamBuilder, DelimitedData}; +use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenCursor, TokenStream}; #[test] fn test_token_stream_iter() { @@ -11,3 +12,33 @@ fn test_token_stream_iter() { let iter = ts.iter(); assert_eq!(iter.size_hint(), (1, Some(1))); } + +#[test] +fn foo() { + let mut arena = ArenaTokenStreamBuilder::default(); + let open1 = arena.start_delimited(); + arena.push_token_alone(Token::new(TokenKind::Plus, DUMMY_SP)); + let open2 = arena.start_delimited(); + arena.push_token_alone(Token::new(TokenKind::Plus, DUMMY_SP)); + arena.finish_delimited( + open2, + DelimitedData { + span: DelimSpan::from_single(DUMMY_SP), + spacing: DelimSpacing { open: Spacing::Alone, close: Spacing::Alone }, + delimiter: Delimiter::Parenthesis, + }, + ); + arena.finish_delimited( + open1, + DelimitedData { + span: DelimSpan::from_single(DUMMY_SP), + spacing: DelimSpacing { open: Spacing::Alone, close: Spacing::Alone }, + delimiter: Delimiter::Parenthesis, + }, + ); + + let mut cursor = TokenCursor::new(arena); + for _ in 0..100 { + cursor.next_and_bump(); + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index b50bd485d342d..4c13408104eac 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -1,7 +1,7 @@ use std::convert::identity; use rustc_ast::token::Delimiter; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{DelimSpan, WithTokens}; use rustc_ast::{AttrItem, Attribute, LitKind, ast, token}; use rustc_attr_ir::target::Target; @@ -310,9 +310,12 @@ pub fn parse_cfg_attr( match &cfg_attr.get_normal_item().args { ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => { check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim); - match parse_in(&sess.psess, TokenArena::from_stream(tokens), "`cfg_attr` input", |p| { - parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr) - }) { + match parse_in( + &sess.psess, + ArenaTokenStream::from_stream(tokens), + "`cfg_attr` input", + |p| parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr), + ) { Ok(r) => return Some(r), Err(e) => { let suggestions = CFG_ATTR_TEMPLATE.suggestions( diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index d57a3ddf316e8..8220b5dadcc9e 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -14,7 +14,7 @@ use std::fmt::{Debug, Display}; use std::sync::atomic::{AtomicBool, Ordering}; use rustc_ast::token::{self, Delimiter, MetaVarKind}; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, PathSegment, StmtKind, UnOp, @@ -722,13 +722,13 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { } fn parse( - arena: TokenArena, + stream: ArenaTokenStream, psess: &'sess ParseSess, span: Span, should_emit: ShouldEmit, allow_expr_metavar: AllowExprMetavar, ) -> PResult<'sess, MetaItemListParser> { - let mut parser = Parser::new(psess, arena, None); + let mut parser = Parser::new(psess, stream, None); if let ShouldEmit::ErrorsAndLints { recovery } = should_emit { parser = parser.recovery(recovery); } @@ -765,7 +765,7 @@ impl MetaItemListParser { allow_expr_metavar: AllowExprMetavar, ) -> Result> { MetaItemListParserContext::parse( - TokenArena::from_stream(tokens), + ArenaTokenStream::from_stream(tokens), psess, span, should_emit, diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index 6cd12ef09db79..4b8c99590cee3 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -4,7 +4,7 @@ use std::convert::identity; use std::slice; use rustc_ast::token::Delimiter; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{ self as ast, AttrArgs, AttrKind, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, @@ -76,9 +76,10 @@ pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Met AttrArgs::Empty => MetaItemKind::Word, AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { check_meta_bad_delim(psess, *dspan, *delim); - let nmis = parse_in(psess, TokenArena::from_stream(tokens), "meta list", |p| { - p.parse_meta_seq_top() - })?; + let nmis = + parse_in(psess, ArenaTokenStream::from_stream(tokens), "meta list", |p| { + p.parse_meta_seq_top() + })?; MetaItemKind::List(nmis) } AttrArgs::Eq { expr, .. } => { diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 0cb583c77ff52..8ad47ea566a0e 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -2,7 +2,7 @@ use core::ops::ControlFlow; use rustc_ast as ast; use rustc_ast::mut_visit::MutVisitor; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{Attribute, HasTokens, NodeId, mut_visit, visit}; use rustc_errors::PResult; @@ -107,7 +107,7 @@ impl CfgEval<'_> { // After that we have our re-parsed `AttrTokenStream`, recursively configuring // our attribute target will correctly configure the tokens as well. let mut parser = - Parser::new(&self.0.sess.psess, TokenArena::from_stream(&orig_tokens), None); + Parser::new(&self.0.sess.psess, ArenaTokenStream::from_stream(&orig_tokens), None); parser.capture_cfg = true; let res: PResult<'_, Option> = try { match &annotatable { diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index d48fb3bdacb06..def95be041f20 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast::attr::MarkedAttrs; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind, Safety}; @@ -1259,7 +1259,7 @@ impl<'a> ExtCtxt<'a> { expand::MacroExpander::new(self, true) } pub fn new_parser_from_tts(&self, stream: TokenStream) -> Parser<'a> { - Parser::new(&self.sess.psess, TokenArena::from_stream(&stream), MACRO_ARGUMENTS) + Parser::new(&self.sess.psess, ArenaTokenStream::from_stream(&stream), MACRO_ARGUMENTS) } pub fn source_map(&self) -> &'a SourceMap { self.sess.psess.source_map() diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index a1f8624deebc2..7f35c9362be7e 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::tokenarena::{DelimitedBounds, DelimitedData, TokenArena}; +use rustc_ast::tokenarena::{ArenaTokenStream, DelimitedBounds, DelimitedData}; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId, Safety}; use rustc_ast_pretty::pprust; @@ -133,7 +133,7 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { matched_rule_bindings: &'b [MatcherLoc], ) -> Self { Self { - parser: Parser::new(&cx.sess.psess, TokenArena::from_stream(&tts), None), + parser: Parser::new(&cx.sess.psess, ArenaTokenStream::from_stream(&tts), 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 @@ -797,7 +797,7 @@ pub fn compile_declarative_macro( let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; - let body = TokenArena::from_stream(¯o_def.body.tokens); + let body = ArenaTokenStream::from_stream(¯o_def.body.tokens); let mut p = Parser::new(&sess.psess, body, rustc_parse::MACRO_ARGUMENTS); // Don't abort iteration early, so that multiple errors can be reported. We only abort early on @@ -827,7 +827,7 @@ pub fn compile_declarative_macro( let args = tt.to_delimited_data(); check_args_parens(sess, sym::attr, args); let args = parse_one_tt( - tt.to_token_tree(p.arena()), + tt.to_token_tree(p.token_stream()), RulePart::Pattern, sess, node_id, @@ -882,7 +882,7 @@ pub fn compile_declarative_macro( } (None, false) }; - let lhs_tt = p.parse_token_tree().to_token_tree(p.arena()); + let lhs_tt = p.parse_token_tree().to_token_tree(p.token_stream()); let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition); check_emission(check_lhs(sess, features, node_id, &lhs_tt)); if let Err(e) = p.expect(exp!(FatArrow)) { @@ -891,7 +891,7 @@ pub fn compile_declarative_macro( if let Some(guar) = check_no_eof(sess, &p, "expected right-hand side of macro rule") { return dummy_syn_ext(guar); } - let rhs = p.parse_token_tree().to_token_tree(p.arena()); + let rhs = p.parse_token_tree().to_token_tree(p.token_stream()); let rhs = parse_one_tt(rhs, RulePart::Body, sess, node_id, features, edition); check_emission(check_rhs(sess, &rhs)); check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs)); @@ -1886,6 +1886,6 @@ pub(super) fn parser_from_cx( recovery: Recovery, ) -> Parser<'_> { tts.desugar_doc_comments(); - Parser::new(psess, TokenArena::from_stream(&tts), rustc_parse::MACRO_ARGUMENTS) + Parser::new(psess, ArenaTokenStream::from_stream(&tts), rustc_parse::MACRO_ARGUMENTS) .recovery(recovery) } diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 18379d61c8e0e..30c53b9f0604a 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,5 +1,5 @@ use rustc_ast as ast; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_data_structures::AtomicRef; use rustc_data_structures::profiling::TimingGuard; @@ -127,7 +127,7 @@ impl MultiItemModifier for DeriveProcMacro { let error_count_before = ecx.dcx().err_count(); let mut parser = Parser::new( &ecx.sess.psess, - TokenArena::from_stream(&output), + ArenaTokenStream::from_stream(&output), Some("proc-macro derive"), ); let mut items = vec![]; diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 373f24a97ea7a..00b851c94314b 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -2,7 +2,7 @@ use std::ops::{Bound, Range}; use rustc_ast as ast; use rustc_ast::token as tk; -use rustc_ast::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{self, DelimSpacing, Spacing, TokenStream}; use rustc_ast::util::literal::escape_byte_str_symbol; use rustc_ast_pretty::pprust; @@ -592,7 +592,7 @@ impl server::Server for Rustc<'_, '_> { let expr = try { let mut p = Parser::new( self.psess(), - TokenArena::from_stream(stream), + ArenaTokenStream::from_stream(stream), Some("proc_macro expand expr"), ); let expr = p.parse_expr()?; diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index e1a0cf3f996e6..554365c6fd9bf 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::tokenarena::TokenArena; +use rustc_ast::tokenarena::ArenaTokenStreamBuilder; 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}; @@ -67,7 +67,7 @@ pub(crate) fn lex_token_trees<'psess, 'src>( psess: &'psess ParseSess, mut src: &'src str, mut start_pos: BytePos, - arena: &mut TokenArena, + arena: &mut ArenaTokenStreamBuilder, override_span: Option, strip_tokens: StripTokens, ) -> Result<(), Vec>> { diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index e309c719f4260..9d49fffb452cb 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::tokenarena::{ArenaTokenTree, DelimitedData, TokenArena}; +use rustc_ast::tokenarena::{ArenaTokenStreamBuilder, ArenaTokenTree, DelimitedData}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing}; use rustc_ast_pretty::pprust::token_to_string; use rustc_errors::Diag; @@ -14,7 +14,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // opening delimiter. pub(super) fn lex_token_trees( &mut self, - arena: &mut TokenArena, + arena: &mut ArenaTokenStreamBuilder, is_delimited: bool, ) -> Result> { // Move past the opening delimiter. @@ -52,7 +52,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { fn lex_token_tree_open_delim( &mut self, - arena: &mut TokenArena, + token_builder: &mut ArenaTokenStreamBuilder, open_delim: Delimiter, ) -> Result> { // The span for beginning of the delimited section. @@ -65,9 +65,9 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // uses an incorrect delimiter. // We remember where we were in the arena, so that we can check how many trees were parsed - let index = arena.length(); - let open_spacing = self.lex_token_trees(arena, /* is_delimited */ true)?; - let lexed_trees = arena.length() - index; + let index = token_builder.length(); + let open_spacing = self.lex_token_trees(token_builder, /* is_delimited */ true)?; + let lexed_trees = token_builder.length() - index; // Expand to cover the entire delimited token tree. let delim_span = DelimSpan::from_pair(pre_span, self.token.span); @@ -97,7 +97,8 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // A brace-delimited block whose first token is `&&`/`||` usually means // the user meant to continue an if-let chain, e.g. `if let P = e { && cond {`. if Delimiter::Brace == open_delim - && let Some(ArenaTokenTree::Token(tok, _)) = arena.get_innermost_elem_at(index) + && let Some(ArenaTokenTree::Token(tok, _)) = + token_builder.get_innermost_elem_at(index) && matches!(tok.kind, token::AndAnd | token::OrOr) { self.diag_info.if_let_chain_hint_spans.push(tok.span); diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index dcafa9fd111bc..edfaf6a9f8790 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -29,7 +29,9 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); #[macro_use] pub mod parser; use parser::Parser; -use rustc_ast::tokenarena::{ArenaTokenTree, DelimitedData, TokenArena}; +use rustc_ast::tokenarena::{ + ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, DelimitedData, +}; use crate::lexer::StripTokens; @@ -246,7 +248,7 @@ pub fn source_str_to_stream( name: FileName, source: String, override_span: Option, -) -> Result>> { +) -> Result>> { let source_file = psess.source_map().new_source_file(name, source); // FIXME(frontmatter): Consider stripping frontmatter in a future edition. We can't strip them // in the current edition since that would be breaking. @@ -263,7 +265,7 @@ fn source_file_to_stream<'psess>( source_file: Arc, override_span: Option, strip_tokens: StripTokens, -) -> Result>> { +) -> Result>> { let src = source_file.src.as_ref().unwrap_or_else(|| { psess.dcx().bug(format!( "cannot lex `source_file` without source: {}", @@ -271,22 +273,22 @@ fn source_file_to_stream<'psess>( )); }); - let mut arena = TokenArena::default(); + let mut token_builder = ArenaTokenStreamBuilder::default(); lexer::lex_token_trees( psess, src.as_str(), source_file.start_pos, - &mut arena, + &mut token_builder, override_span, strip_tokens, )?; - Ok(arena) + Ok(token_builder.finish()) } /// Runs the given subparser `f` on the tokens of the given `attr`'s item. pub fn parse_in<'a, T>( psess: &'a ParseSess, - arena: TokenArena, + arena: ArenaTokenStream, name: &'static str, mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>, ) -> PResult<'a, T> { @@ -302,9 +304,9 @@ pub fn fake_token_stream_for_item( psess: &ParseSess, item: &ast::Item, attr_to_exclude: Option<&ast::Attribute>, -) -> TokenArena { - if let Some(arena) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { - return arena; +) -> ArenaTokenStream { + if let Some(stream) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { + return stream; } let source = pprust::item_to_string(item); @@ -316,7 +318,7 @@ fn fake_token_stream_for_file_mod( psess: &ParseSess, item: &ast::Item, attr_to_exclude: Option<&ast::Attribute>, -) -> Option { +) -> Option { let ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(_, ast::Inline::No { .. }, spans)) = &item.kind else { @@ -326,7 +328,7 @@ fn fake_token_stream_for_file_mod( let attr = attr_to_exclude.expect("file modules must have an attribute to exclude"); assert_eq!(attr.style, ast::AttrStyle::Inner); - let mut arena = TokenArena::default(); + let mut arena = ArenaTokenStreamBuilder::default(); for attr in item.attrs.iter().filter(|attr| attr.style == ast::AttrStyle::Outer) { attr.push_token_trees(&mut arena); @@ -354,10 +356,14 @@ fn fake_token_stream_for_file_mod( delimiter: token::Delimiter::Brace, }, ); - Some(arena) + Some(arena.finish()) } -fn lex_token_trees_for_span(psess: &ParseSess, span: Span, arena: &mut TokenArena) -> Option<()> { +fn lex_token_trees_for_span( + psess: &ParseSess, + span: Span, + arena: &mut ArenaTokenStreamBuilder, +) -> Option<()> { let src = psess.source_map().span_to_snippet(span).ok()?; match lexer::lex_token_trees(psess, &src, span.lo(), arena, None, StripTokens::Nothing) { Ok(_) => Some(()), @@ -371,13 +377,13 @@ fn lex_token_trees_for_span(psess: &ParseSess, span: Span, arena: &mut TokenAren pub fn fake_token_stream_for_foreign_item( psess: &ParseSess, item: &ast::ForeignItem, -) -> TokenArena { +) -> ArenaTokenStream { let source = pprust::foreign_item_to_string(item); let filename = FileName::macro_expansion_source_code(&source); unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))) } -pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenArena { +pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> ArenaTokenStream { let source = pprust::crate_to_string_for_macros(krate); let filename = FileName::macro_expansion_source_code(&source); unwrap_or_emit_fatal(source_str_to_stream( diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index dd4c69953620c..0c447bffb0110 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -25,7 +25,7 @@ impl<'a> Parser<'a> { tree @ ArenaTokenTree::DelimitedStart(..) => { // Optionally end with a comma. let _ = self.eat(exp!(Comma)); - return Ok(match tree.to_token_tree(&self.token_cursor.arena) { + return Ok(match tree.to_token_tree(&self.token_cursor.stream) { TokenTree::Token(_, _) => unreachable!(), TokenTree::Delimited(_, _, _, tts) => tts, }); diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 5cd4dbd09cea7..260d0baf495f2 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2604,9 +2604,9 @@ impl<'a> Parser<'a> { let bspan = body.span(); let arrow = TokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` let tokens = TokenStream::new(vec![ - params.to_token_tree(&self.token_cursor.arena), + params.to_token_tree(&self.token_cursor.stream), arrow, - body.to_token_tree(&self.token_cursor.arena), + body.to_token_tree(&self.token_cursor.stream), ]); let dspan = DelimSpan::from_pair(pspan.shrink_to_lo(), bspan.shrink_to_hi()); Box::new(DelimArgs { dspan, delim: Delimiter::Brace, tokens }) diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index dfdc8a70d2e71..1f251ce8ed3a2 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -29,7 +29,7 @@ pub use path::PathStyle; use rustc_ast::token::{ self, IdentIsRaw, InvisibleOrigin, MetaVarKind, NtExprKind, NtPatKind, Token, TokenKind, }; -use rustc_ast::tokenarena::{ArenaTokenTree, TokenArena}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; use rustc_ast::tokenstream::{ ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, WithTokens, }; @@ -245,8 +245,8 @@ pub struct Parser<'a> { } impl<'a> Parser<'a> { - pub fn arena(&self) -> &TokenArena { - &self.token_cursor.arena + pub fn token_stream(&self) -> &ArenaTokenStream { + &self.token_cursor.stream } } @@ -349,12 +349,12 @@ pub fn token_descr(token: &Token) -> String { impl<'a> Parser<'a> { pub fn new( psess: &'a ParseSess, - arena: TokenArena, + stream: ArenaTokenStream, subparser_name: Option<&'static str>, ) -> Self { let mut parser = Parser { psess, - token_cursor: TokenCursor::new(arena), + token_cursor: TokenCursor::new(stream), subparser_name, capture_state: CaptureState { capturing: Capturing::No, @@ -1387,7 +1387,7 @@ impl<'a> Parser<'a> { delimited.then(|| { let TokenTree::Delimited(dspan, _, delim, tokens) = - self.parse_token_tree().to_token_tree(&self.token_cursor.arena) + self.parse_token_tree().to_token_tree(&self.token_cursor.stream) else { unreachable!() }; @@ -1451,7 +1451,7 @@ impl<'a> Parser<'a> { } } TokenStream::new( - result.into_iter().map(|tt| tt.to_token_tree(&self.token_cursor.arena)).collect(), + result.into_iter().map(|tt| tt.to_token_tree(&self.token_cursor.stream)).collect(), ) } diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 67f8fd0964b34..f07792f23ef54 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -126,7 +126,7 @@ impl<'a> Parser<'a> { match kind { // Note that TT is treated differently to all the others. NonterminalKind::TT => Ok(ParseNtResult::Tt( - self.parse_token_tree().to_token_tree(&self.token_cursor.arena), + self.parse_token_tree().to_token_tree(&self.token_cursor.stream), )), NonterminalKind::Item => match self .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? From f38ebbc86a169f1f15f9afd09addaaa39648a9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 10:14:50 +0200 Subject: [PATCH 11/25] Store the parent of each delimited sequence explicitly --- compiler/rustc_ast/src/tokenarena.rs | 30 ++++++++++++++-- compiler/rustc_ast/src/tokenstream.rs | 34 +++++++++++-------- compiler/rustc_parse/src/parser/mod.rs | 2 +- src/librustdoc/clean/render_macro_matchers.rs | 2 +- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 09bc1f938ca0a..cb1c4da892341 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -57,11 +57,13 @@ impl ArenaTokenTree { } } -static_assert_size!(ArenaTokenTree, 36); +static_assert_size!(ArenaTokenTree, 40); #[derive(Debug, Default)] pub struct ArenaTokenStreamBuilder { tokens: Vec, + /// Index of the current delimited sequence + current_delimited_sequence: Option, } impl ArenaTokenStreamBuilder { @@ -99,8 +101,10 @@ impl ArenaTokenStreamBuilder { pub fn start_delimited(&mut self) -> OpenDelimited { let index = self.length(); + let parent = self.current_delimited_sequence.replace(index); + self.tokens.push(ArenaTokenTree::DelimitedStart( - DelimitedBounds { start: index as u32, length: 0 }, + DelimitedBounds { start: index as u32, length: 0, parent: parent.map(|v| v as u32) }, DelimitedData { span: DelimSpan { open: Default::default(), close: Default::default() }, spacing: DelimSpacing { open: Spacing::Alone, close: Spacing::Alone }, @@ -120,6 +124,7 @@ impl ArenaTokenStreamBuilder { let len = length.saturating_sub(open.start); bounds.length = len as u32; *data = delimited_data; + self.current_delimited_sequence = bounds.parent.map(|v| v as usize); } } } @@ -157,6 +162,19 @@ impl ArenaTokenStream { self.tokens.is_empty() } + pub fn get_parent_of( + &self, + bounds: DelimitedBounds, + ) -> Option<(DelimitedBounds, DelimitedData)> { + let parent = bounds.parent?; + match self.tokens.get(parent as usize).expect("Parent index was not found") { + ArenaTokenTree::Token(..) => { + panic!("DelimitedBounds parent index points to a token. This is a bug."); + } + ArenaTokenTree::DelimitedStart(bounds, data) => Some((*bounds, *data)), + } + } + pub fn to_token_stream(&self) -> TokenStream { let mut tokens = vec![]; for tt in self.iter_top_level_trees() { @@ -170,7 +188,10 @@ impl ArenaTokenStream { } pub fn from_stream(stream: &TokenStream) -> Self { - let mut arena = ArenaTokenStreamBuilder { tokens: Vec::with_capacity(stream.len()) }; + let mut arena = ArenaTokenStreamBuilder { + tokens: Vec::with_capacity(stream.len()), + current_delimited_sequence: None, + }; arena.fill(stream); arena.finish() } @@ -229,6 +250,9 @@ pub struct DelimitedBounds { /// The length includes both the start and the end token. /// So an empty delimited sequence has length 2. pub length: u32, + /// Index of the parent of the current delimited sequence. + /// If this is the root delimited sequence, is `None`. + pub parent: Option, } impl DelimitedBounds { diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 986dcfa758b2b..da87b0a75268d 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -908,15 +908,16 @@ pub struct TokenCursor { /// Global index into the token arena. index: usize, delimited_sequence_end: usize, - /// The current delimited sequences that we are inside of. - stack: Vec<(DelimitedBounds, DelimitedData)>, + depth: usize, + /// The current delimited sequence that we are inside of, if any. + parent: Option<(DelimitedBounds, DelimitedData)>, } impl TokenCursor { #[inline] pub fn new(stream: ArenaTokenStream) -> Self { let end = stream.length() + 1; - TokenCursor { stream, index: 0, delimited_sequence_end: end, stack: vec![] } + TokenCursor { stream, index: 0, delimited_sequence_end: end, depth: 0, parent: None } } /// Gets the next token and advances the cursor by one. @@ -960,7 +961,7 @@ impl TokenCursor { /// delimited sequence. Panics if we are not within a delimited sequence. #[inline] pub fn look_ahead_past_close_delim(&self) -> Option<&ArenaTokenTree> { - let (bounds, _) = self.stack.last().unwrap(); + let (bounds, _) = self.parent.as_ref().unwrap(); self.stream.get_innermost_elem_at(bounds.index_of_next_token_tree()) } @@ -968,14 +969,14 @@ impl TokenCursor { /// a delimited sequence. #[inline] pub fn clone_enclosing_delim(&self) -> ArenaTokenTree { - let &(bounds, data) = self.stack.last().unwrap(); + let &(bounds, data) = self.parent.as_ref().unwrap(); ArenaTokenTree::DelimitedStart(bounds, data) } /// For skipping to the end of the current sequence, in rare circumstances. #[inline] pub fn bump_to_end(&mut self) { - if let Some((bounds, _)) = self.stack.last() { + if let Some((bounds, _)) = self.parent.as_ref() { self.index = bounds.index_of_next_token_tree(); } else { self.index = self.stream.length(); @@ -985,13 +986,13 @@ impl TokenCursor { /// Note: the outermost stream has depth of 0. #[inline] pub fn depth(&self) -> usize { - self.stack.len() + self.depth } /// Returns details about the parent delimited sequence, if there is one. #[inline] pub fn parent_delim_and_span(&self) -> Option<(Delimiter, DelimSpan)> { - if let Some((_, data)) = self.stack.last() { + if let Some((_, data)) = self.parent.as_ref() { Some((data.delimiter, data.span)) } else { None @@ -1003,12 +1004,16 @@ impl TokenCursor { pub fn inlined_next_and_bump(&mut self) -> (Token, Spacing) { loop { if self.index == self.delimited_sequence_end { - let (_, data) = self.stack.pop().unwrap(); + let (bounds, data) = self.parent.take().unwrap(); + self.depth -= 1; + + // Find the previous parent + self.parent = self.stream.get_parent_of(bounds); // How much is left for the now-current sequence? self.delimited_sequence_end = self - .stack - .last() + .parent + .as_ref() .map(|(bounds, _)| bounds.index_of_next_token_tree()) .unwrap_or(self.stream.length() + 1); @@ -1032,9 +1037,10 @@ impl TokenCursor { return (token, spacing); } &ArenaTokenTree::DelimitedStart(bounds, data) => { - self.stack.push((bounds, data)); self.index += 1; + self.depth += 1; self.delimited_sequence_end = bounds.index_of_next_token_tree(); + self.parent = Some((bounds, data)); if !data.delimiter.skip() { return ( Token::new(data.delimiter.as_open_token_kind(), data.span.open), @@ -1045,7 +1051,7 @@ impl TokenCursor { } } } else { - assert!(self.stack.is_empty()); + assert!(self.parent.is_none()); // We have exhausted the outermost token stream. The use of // `Spacing::Alone` is arbitrary and immaterial, because the @@ -1103,7 +1109,7 @@ mod size_asserts { static_assert_size!(AttrTokenStream, 8); static_assert_size!(AttrTokenTree, 32); static_assert_size!(LazyAttrTokenStream, 8); - static_assert_size!(LazyAttrTokenStreamInner, 96); + static_assert_size!(LazyAttrTokenStreamInner, 120); static_assert_size!(Option, 8); // must be small, used in many AST nodes static_assert_size!(TokenStream, 8); static_assert_size!(TokenTree, 32); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 1f251ce8ed3a2..c463def16613b 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -254,7 +254,7 @@ impl<'a> Parser<'a> { // 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<'_>, 304); +rustc_data_structures::static_assert_size!(Parser<'_>, 320); /// Stores span information about a closure. #[derive(Clone, Debug)] diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs index 70498dd3ef090..444eafb2e7dc8 100644 --- a/src/librustdoc/clean/render_macro_matchers.rs +++ b/src/librustdoc/clean/render_macro_matchers.rs @@ -88,7 +88,7 @@ fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option Date: Fri, 11 Sep 2026 10:24:33 +0200 Subject: [PATCH 12/25] Reduce size of `TokenCursor` --- compiler/rustc_ast/src/tokenarena.rs | 7 ++---- compiler/rustc_ast/src/tokenstream.rs | 35 +++++++++++++++++--------- compiler/rustc_parse/src/parser/mod.rs | 2 +- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index cb1c4da892341..152abe4feb291 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -162,16 +162,13 @@ impl ArenaTokenStream { self.tokens.is_empty() } - pub fn get_parent_of( - &self, - bounds: DelimitedBounds, - ) -> Option<(DelimitedBounds, DelimitedData)> { + pub fn get_parent_of(&self, bounds: DelimitedBounds) -> Option { let parent = bounds.parent?; match self.tokens.get(parent as usize).expect("Parent index was not found") { ArenaTokenTree::Token(..) => { panic!("DelimitedBounds parent index points to a token. This is a bug."); } - ArenaTokenTree::DelimitedStart(bounds, data) => Some((*bounds, *data)), + ArenaTokenTree::DelimitedStart(bounds, _) => Some(*bounds), } } diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index da87b0a75268d..78c1190194d8f 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -908,9 +908,9 @@ pub struct TokenCursor { /// Global index into the token arena. index: usize, delimited_sequence_end: usize, - depth: usize, + depth: u32, /// The current delimited sequence that we are inside of, if any. - parent: Option<(DelimitedBounds, DelimitedData)>, + parent: Option, } impl TokenCursor { @@ -961,7 +961,7 @@ impl TokenCursor { /// delimited sequence. Panics if we are not within a delimited sequence. #[inline] pub fn look_ahead_past_close_delim(&self) -> Option<&ArenaTokenTree> { - let (bounds, _) = self.parent.as_ref().unwrap(); + let bounds = self.parent.as_ref().unwrap(); self.stream.get_innermost_elem_at(bounds.index_of_next_token_tree()) } @@ -969,14 +969,14 @@ impl TokenCursor { /// a delimited sequence. #[inline] pub fn clone_enclosing_delim(&self) -> ArenaTokenTree { - let &(bounds, data) = self.parent.as_ref().unwrap(); - ArenaTokenTree::DelimitedStart(bounds, data) + let bounds = self.parent.as_ref().unwrap(); + ArenaTokenTree::DelimitedStart(*bounds, self.get_delimited_data(bounds)) } /// For skipping to the end of the current sequence, in rare circumstances. #[inline] pub fn bump_to_end(&mut self) { - if let Some((bounds, _)) = self.parent.as_ref() { + if let Some(bounds) = self.parent.as_ref() { self.index = bounds.index_of_next_token_tree(); } else { self.index = self.stream.length(); @@ -986,25 +986,35 @@ impl TokenCursor { /// Note: the outermost stream has depth of 0. #[inline] pub fn depth(&self) -> usize { - self.depth + self.depth as usize } /// Returns details about the parent delimited sequence, if there is one. #[inline] pub fn parent_delim_and_span(&self) -> Option<(Delimiter, DelimSpan)> { - if let Some((_, data)) = self.parent.as_ref() { + if let Some(bounds) = self.parent.as_ref() { + let data = self.get_delimited_data(bounds); Some((data.delimiter, data.span)) } else { None } } + fn get_delimited_data(&self, bounds: &DelimitedBounds) -> DelimitedData { + let Some(ArenaTokenTree::DelimitedStart(_, data)) = + self.stream.get_innermost_elem_at(bounds.start as usize) + else { + panic!("Delimited sequence not found at the provided bounds"); + }; + *data + } + /// This always-inlined version should only be used on hot code paths. #[inline(always)] pub fn inlined_next_and_bump(&mut self) -> (Token, Spacing) { loop { if self.index == self.delimited_sequence_end { - let (bounds, data) = self.parent.take().unwrap(); + let bounds = self.parent.take().unwrap(); self.depth -= 1; // Find the previous parent @@ -1014,9 +1024,10 @@ impl TokenCursor { self.delimited_sequence_end = self .parent .as_ref() - .map(|(bounds, _)| bounds.index_of_next_token_tree()) + .map(|bounds| bounds.index_of_next_token_tree()) .unwrap_or(self.stream.length() + 1); + let data = self.get_delimited_data(&bounds); if !data.delimiter.skip() { return ( Token::new(data.delimiter.as_close_token_kind(), data.span.close), @@ -1040,7 +1051,7 @@ impl TokenCursor { self.index += 1; self.depth += 1; self.delimited_sequence_end = bounds.index_of_next_token_tree(); - self.parent = Some((bounds, data)); + self.parent = Some(bounds); if !data.delimiter.skip() { return ( Token::new(data.delimiter.as_open_token_kind(), data.span.open), @@ -1109,7 +1120,7 @@ mod size_asserts { static_assert_size!(AttrTokenStream, 8); static_assert_size!(AttrTokenTree, 32); static_assert_size!(LazyAttrTokenStream, 8); - static_assert_size!(LazyAttrTokenStreamInner, 120); + static_assert_size!(LazyAttrTokenStreamInner, 96); static_assert_size!(Option, 8); // must be small, used in many AST nodes static_assert_size!(TokenStream, 8); static_assert_size!(TokenTree, 32); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index c463def16613b..1f251ce8ed3a2 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -254,7 +254,7 @@ impl<'a> Parser<'a> { // 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<'_>, 320); +rustc_data_structures::static_assert_size!(Parser<'_>, 304); /// Stores span information about a closure. #[derive(Clone, Debug)] From 42e998f90789e789f9476736f8b321923e1244b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 17:56:07 +0200 Subject: [PATCH 13/25] Store `ArenaTokenStream` in `DelimArgs` --- compiler/rustc_ast/src/ast.rs | 5 +- compiler/rustc_ast/src/attr/mod.rs | 10 +- compiler/rustc_ast/src/tokenarena.rs | 132 +++++++++++++++--- compiler/rustc_ast/src/tokenstream/tests.rs | 4 +- compiler/rustc_ast/src/visit.rs | 1 + compiler/rustc_ast_lowering/src/lib.rs | 5 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 6 +- compiler/rustc_attr_ir/src/attr.rs | 2 +- .../rustc_attr_parsing/src/attributes/cfg.rs | 10 +- compiler/rustc_attr_parsing/src/parser.rs | 4 +- .../rustc_attr_parsing/src/validate_attr.rs | 5 +- compiler/rustc_builtin_macros/src/assert.rs | 3 +- .../src/assert/context.rs | 48 ++++--- compiler/rustc_builtin_macros/src/autodiff.rs | 36 +++-- .../src/deriving/generic/mod.rs | 31 ++-- .../src/deriving/reborrow.rs | 2 +- .../rustc_builtin_macros/src/edition_panic.rs | 3 +- compiler/rustc_builtin_macros/src/eii.rs | 35 ++--- compiler/rustc_builtin_macros/src/offload.rs | 15 +- compiler/rustc_expand/src/build.rs | 6 +- compiler/rustc_expand/src/expand.rs | 23 +-- compiler/rustc_expand/src/mbe/macro_rules.rs | 2 +- compiler/rustc_expand/src/placeholders.rs | 3 +- compiler/rustc_hir_pretty/src/lib.rs | 2 +- compiler/rustc_lint/src/builtin.rs | 4 +- ..._expr_fragment_specifier_2024_migration.rs | 2 +- compiler/rustc_parse/src/lexer/tokentrees.rs | 2 +- compiler/rustc_parse/src/lib.rs | 2 +- compiler/rustc_parse/src/parser/item.rs | 13 +- compiler/rustc_parse/src/parser/mod.rs | 32 +++-- src/librustdoc/clean/utils.rs | 7 +- src/librustdoc/doctest/make.rs | 3 +- 32 files changed, 277 insertions(+), 181 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index c14ad62e9a60b..9191848aa2c26 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -378,6 +378,7 @@ impl ParenthesizedArgs { } pub use crate::node_id::{CRATE_NODE_ID, DUMMY_NODE_ID, NodeId}; +use crate::tokenarena::ArenaTokenStream; /// Modifiers on a trait bound like `[const]`, `?` and `!`. #[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug, Walkable)] @@ -2093,7 +2094,7 @@ impl AttrArgs { pub fn inner_tokens(&self) -> TokenStream { match self { AttrArgs::Empty => TokenStream::default(), - AttrArgs::Delimited(args) => args.tokens.clone(), + AttrArgs::Delimited(args) => args.tokens.to_token_stream(), AttrArgs::Eq { expr, .. } => TokenStream::from_ast(expr), } } @@ -2104,7 +2105,7 @@ impl AttrArgs { pub struct DelimArgs { pub dspan: DelimSpan, pub delim: Delimiter, // Note: `Delimiter::Invisible` never occurs - pub tokens: TokenStream, + pub tokens: ArenaTokenStream, } impl DelimArgs { diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 4086b49ce4c09..12ed43d23c8dd 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -19,7 +19,7 @@ use crate::ast::{ use crate::token::{ self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, }; -use crate::tokenarena::ArenaTokenStreamBuilder; +use crate::tokenarena::{ArenaTokenStream, ArenaTokenStreamBuilder}; use crate::tokenstream::{ AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, @@ -368,7 +368,7 @@ impl AttrItem { pub fn meta_item_list(&self) -> Option> { match &self.args { AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { - MetaItemKind::list_from_tokens(args.tokens.clone()) + MetaItemKind::list_from_tokens(args.tokens.to_token_stream()) } AttrArgs::Delimited(_) | AttrArgs::Eq { .. } | AttrArgs::Empty => None, } @@ -613,7 +613,7 @@ impl MetaItemKind { match args { AttrArgs::Empty => Some(MetaItemKind::Word), AttrArgs::Delimited(DelimArgs { dspan: _, delim: Delimiter::Parenthesis, tokens }) => { - MetaItemKind::list_from_tokens(tokens.clone()).map(MetaItemKind::List) + MetaItemKind::list_from_tokens(tokens.to_token_stream()).map(MetaItemKind::List) } AttrArgs::Delimited(..) => None, AttrArgs::Eq { expr, .. } => match expr.kind { @@ -826,10 +826,10 @@ pub fn mk_attr_nested_word( inner: Symbol, span: Span, ) -> Attribute { - let inner_tokens = TokenStream::new(vec![TokenTree::Token( + let inner_tokens = ArenaTokenStream::from_token( Token::from_ast_ident(Ident::new(inner, span)), Spacing::Alone, - )]); + ); let outer_ident = Ident::new(outer, span); let path = Path::from_ident(outer_ident); let attr_args = AttrArgs::Delimited(DelimArgs { diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 152abe4feb291..0184ec49e056d 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; use rustc_index::static_assert_size; use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Span; @@ -8,7 +9,8 @@ use crate::token::{Delimiter, Token, TokenKind}; use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; /// Part of a `TokenArena`. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] +#[derive(StableHash)] // FIXME: is this Ok? pub enum ArenaTokenTree { /// A single token. Should never be `OpenDelim` or `CloseDelim`, because /// delimiters are implicitly represented by `DelimitedStart`/`DelimitedEnd`. @@ -23,6 +25,11 @@ impl ArenaTokenTree { ArenaTokenTree::Token(Token::new(kind, span), Spacing::Alone) } + /// Create a `TokenTree::Token` with joint spacing. + pub fn token_joint(kind: TokenKind, span: Span) -> ArenaTokenTree { + ArenaTokenTree::Token(Token::new(kind, span), Spacing::Joint) + } + /// Convert an arena token tree to the tree-shaped token tree. pub fn to_token_tree(&self, arena: &ArenaTokenStream) -> TokenTree { match self { @@ -67,6 +74,10 @@ pub struct ArenaTokenStreamBuilder { } impl ArenaTokenStreamBuilder { + pub fn with_capacity(capacity: usize) -> Self { + Self { tokens: Vec::with_capacity(capacity), current_delimited_sequence: None } + } + pub fn push_token(&mut self, token: Token, spacing: Spacing) { self.tokens.push(ArenaTokenTree::Token(token, spacing)); } @@ -91,7 +102,7 @@ impl ArenaTokenStreamBuilder { TokenTree::Delimited(span, spacing, delimiter, stream) => { let start = self.start_delimited(); self.fill(stream); - self.finish_delimited( + self.close_delimited( start, DelimitedData { span: *span, spacing: *spacing, delimiter: *delimiter }, ); @@ -114,7 +125,7 @@ impl ArenaTokenStreamBuilder { OpenDelimited { start: index } } - pub fn finish_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { + pub fn close_delimited(&mut self, open: OpenDelimited, delimited_data: DelimitedData) { let length = self.length(); match &mut self.tokens[open.start] { ArenaTokenTree::Token(..) => { @@ -129,6 +140,11 @@ impl ArenaTokenStreamBuilder { } } + pub fn empty_delimited(&mut self, delimited_data: DelimitedData) { + let start = self.start_delimited(); + self.close_delimited(start, delimited_data); + } + pub fn get_innermost_elem_at(&self, index: usize) -> Option<&ArenaTokenTree> { self.tokens.get(index) } @@ -154,6 +170,93 @@ pub struct ArenaTokenStream { } impl ArenaTokenStream { + /// Note: using this function is potentially dangerous, because the caller has to ensure that + /// if `tokens` contains any delimited sequences, their indices are lined up and do not refer + /// to anything existing outside of the passed set of tokens. + /// That is why the function is private. + pub fn from_token_vec(tokens: Vec<(Token, Spacing)>) -> Self { + // FIXME: solve this in a better way + Self { + tokens: Arc::new( + tokens + .into_iter() + .map(|(token, spacing)| ArenaTokenTree::Token(token, spacing)) + .collect(), + ), + } + } + + /// Create a new stream out of the token trees. + /// We might need to copy out children trees out of `stream`, if `tokens` contains any + /// delimited sequences. + /// We also need to reparent those to fix-up the parent indices. + pub fn new_reparented(trees: &[ArenaTokenTree], stream: &ArenaTokenStream) -> Self { + let mut builder = ArenaTokenStreamBuilder::with_capacity(trees.len()); + // FIXME: implement this in a more performant way + for tree in trees { + let tree = tree.to_token_tree(stream); + builder.push_token_tree(&tree); + } + builder.finish() + } + + pub fn from_token(token: Token, spacing: Spacing) -> Self { + Self { tokens: Arc::new(vec![ArenaTokenTree::Token(token, spacing)]) } + } + + pub fn from_stream(stream: &TokenStream) -> Self { + let mut arena = ArenaTokenStreamBuilder { + tokens: Vec::with_capacity(stream.len()), + current_delimited_sequence: None, + }; + arena.fill(stream); + arena.finish() + } + + pub fn to_token_stream(&self) -> TokenStream { + let mut tokens = vec![]; + for tt in self.iter_top_level_trees() { + tokens.push(tt.to_token_tree(self)); + } + TokenStream::new(tokens) + } + + /// Extract **the contents** of a delimited sequence out of this token stream. + /// The delimited sequence start/end is **NOT** returend in the output. + /// `stream` is the original token stream that contains the delimited sequence identified by + /// `bounds`. + pub fn separate_delimited_inner( + bounds: DelimitedBounds, + stream: &ArenaTokenStream, + ) -> ArenaTokenStream { + // eprintln!("separate delimited"); + // This could be implemented in a smarter way by reusing the original allocation + // and storing an index with "view" into it. + let start = bounds.start as usize + 1; + let length = (bounds.length as usize).saturating_sub(1); + + let mut tokens = stream.tokens[start..start + length].to_vec(); + let start = start as u32; + + for tree in &mut tokens { + match tree { + ArenaTokenTree::Token(_, _) => {} + ArenaTokenTree::DelimitedStart(b, _) => { + b.start -= start; + b.parent = b.parent.and_then(|p| { + if p < start { + // Top-level, now we will have no parent + None + } else { + Some(p - start) + } + }); + } + } + } + Self { tokens: Arc::new(tokens) } + } + pub fn length(&self) -> usize { self.tokens.len() } @@ -172,27 +275,10 @@ impl ArenaTokenStream { } } - pub fn to_token_stream(&self) -> TokenStream { - let mut tokens = vec![]; - for tt in self.iter_top_level_trees() { - tokens.push(tt.to_token_tree(self)); - } - TokenStream::new(tokens) - } - pub fn get_innermost_elem_at(&self, index: usize) -> Option<&ArenaTokenTree> { self.tokens.get(index) } - pub fn from_stream(stream: &TokenStream) -> Self { - let mut arena = ArenaTokenStreamBuilder { - tokens: Vec::with_capacity(stream.len()), - current_delimited_sequence: None, - }; - arena.fill(stream); - arena.finish() - } - /// Iter top-level token trees of a delimited token sequence. pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { let mut index = (bounds.start + 1) as usize; @@ -237,6 +323,12 @@ impl ArenaTokenStream { } } +impl StableHash for ArenaTokenStream { + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.tokens.as_slice().stable_hash(hcx, hasher); + } +} + pub struct OpenDelimited { start: usize, } diff --git a/compiler/rustc_ast/src/tokenstream/tests.rs b/compiler/rustc_ast/src/tokenstream/tests.rs index 085a0df81007c..d3559777b7570 100644 --- a/compiler/rustc_ast/src/tokenstream/tests.rs +++ b/compiler/rustc_ast/src/tokenstream/tests.rs @@ -20,7 +20,7 @@ fn foo() { arena.push_token_alone(Token::new(TokenKind::Plus, DUMMY_SP)); let open2 = arena.start_delimited(); arena.push_token_alone(Token::new(TokenKind::Plus, DUMMY_SP)); - arena.finish_delimited( + arena.close_delimited( open2, DelimitedData { span: DelimSpan::from_single(DUMMY_SP), @@ -28,7 +28,7 @@ fn foo() { delimiter: Delimiter::Parenthesis, }, ); - arena.finish_delimited( + arena.close_delimited( open1, DelimitedData { span: DelimSpan::from_single(DUMMY_SP), diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 14ef1c147f253..a4eedf71c630d 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -354,6 +354,7 @@ macro_rules! common_visitor_and_walkers { crate::token::LitKind, crate::tokenstream::LazyAttrTokenStream, crate::tokenstream::TokenStream, + crate::tokenarena::ArenaTokenStream, Movability, Mutability, Pinnedness, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a27dc47bf27c3..849187e30ac57 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -43,6 +43,7 @@ use std::sync::Arc; use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::node_id::NodeMap; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, *}; use rustc_attr_parsing::{AttributeParser, Recovery, ShouldEmit}; @@ -631,7 +632,7 @@ fn index_ast<'tcx>( dummy: impl FnOnce(Box) -> K, ) -> Box> { use rustc_ast::token::Delimiter; - use rustc_ast::tokenstream::{DelimSpan, TokenStream}; + use rustc_ast::tokenstream::DelimSpan; use thin_vec::thin_vec; Box::new(Item { @@ -646,7 +647,7 @@ fn index_ast<'tcx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Parenthesis, - tokens: TokenStream::new(Vec::new()), + tokens: ArenaTokenStream::default(), }), })), tokens: None, diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 977eb0ee4592d..040a53814343c 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -715,7 +715,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere None, *delim, None, - tokens, + &tokens.to_token_stream(), true, span, ), @@ -926,7 +926,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Some(*ident), macro_def.body.delim, None, - ¯o_def.body.tokens, + ¯o_def.body.tokens.to_token_stream(), true, sp, ); @@ -1674,7 +1674,7 @@ impl<'a> State<'a> { None, m.args.delim, None, - &m.args.tokens, + &m.args.tokens.to_token_stream(), true, m.span(), ); diff --git a/compiler/rustc_attr_ir/src/attr.rs b/compiler/rustc_attr_ir/src/attr.rs index 6068c11590a23..5126edd8d8cf5 100644 --- a/compiler/rustc_attr_ir/src/attr.rs +++ b/compiler/rustc_attr_ir/src/attr.rs @@ -154,7 +154,7 @@ impl AttributeExt for Attribute { match &self { Attribute::Unparsed(n) => match n.as_ref() { AttrItem { args: AttrArgs::Delimited(d), .. } => { - ast::MetaItemKind::list_from_tokens(d.tokens.clone()) + ast::MetaItemKind::list_from_tokens(d.tokens.to_token_stream()) } _ => None, }, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 4c13408104eac..8102208ec519d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -1,7 +1,6 @@ use std::convert::identity; use rustc_ast::token::Delimiter; -use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{DelimSpan, WithTokens}; use rustc_ast::{AttrItem, Attribute, LitKind, ast, token}; use rustc_attr_ir::target::Target; @@ -310,12 +309,9 @@ pub fn parse_cfg_attr( match &cfg_attr.get_normal_item().args { ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => { check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim); - match parse_in( - &sess.psess, - ArenaTokenStream::from_stream(tokens), - "`cfg_attr` input", - |p| parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr), - ) { + match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| { + parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr) + }) { Ok(r) => return Some(r), Err(e) => { let suggestions = CFG_ATTR_TEMPLATE.suggestions( diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 8220b5dadcc9e..d3e029e52f99f 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -133,7 +133,7 @@ impl ArgParser { // Therefore we can substitute with a dummy value on invalid syntax. if matches!(parts, [sym::rustc_dummy] | [sym::diagnostic, ..]) { match MetaItemListParser::new( - &args.tokens, + &args.tokens.to_token_stream(), args.dspan.entire(), psess, ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden }, @@ -164,7 +164,7 @@ impl ArgParser { Self::List( MetaItemListParser::new( - &args.tokens, + &args.tokens.to_token_stream(), args.dspan.entire(), psess, should_emit, diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index 4b8c99590cee3..4719ee5103877 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -4,7 +4,6 @@ use std::convert::identity; use std::slice; use rustc_ast::token::Delimiter; -use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{ self as ast, AttrArgs, AttrKind, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, @@ -77,9 +76,7 @@ pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Met AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { check_meta_bad_delim(psess, *dspan, *delim); let nmis = - parse_in(psess, ArenaTokenStream::from_stream(tokens), "meta list", |p| { - p.parse_meta_seq_top() - })?; + parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?; MetaItemKind::List(nmis) } AttrArgs::Eq { expr, .. } => { diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index 106b67d1c8ec7..6c6f4aa404d57 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -1,6 +1,7 @@ mod context; use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token}; use rustc_ast_pretty::pprust; @@ -58,7 +59,7 @@ pub(crate) fn expand_assert<'cx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(call_site_span), delim: Delimiter::Parenthesis, - tokens, + tokens: ArenaTokenStream::from_stream(&tokens), }), })), ); diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 80ea2e3d877fc..987a8886117c1 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -1,5 +1,6 @@ -use rustc_ast::token::{self, Delimiter, IdentIsRaw}; -use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; +use rustc_ast::token::{self, Delimiter, IdentIsRaw, Token}; +use rustc_ast::tokenarena::ArenaTokenStream; +use rustc_ast::tokenstream::{DelimSpan, Spacing}; use rustc_ast::{ BinOpKind, BorrowKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall, Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, @@ -145,30 +146,33 @@ impl<'cx, 'a> Context<'cx, 'a> { fn build_panic(&self, expr_str: &str, panic_path: Path) -> Box { let escaped_expr_str = escape_to_fmt(expr_str); let initial = [ - TokenTree::token_joint( - token::Literal(token::Lit { - kind: token::LitKind::Str, - symbol: Symbol::intern(&if self.fmt_string.is_empty() { - format!("Assertion failed: {escaped_expr_str}") - } else { - format!( - "Assertion failed: {escaped_expr_str}\nWith captures:\n{}", - self.fmt_string - ) + ( + Token::new( + token::Literal(token::Lit { + kind: token::LitKind::Str, + symbol: Symbol::intern(&if self.fmt_string.is_empty() { + format!("Assertion failed: {escaped_expr_str}") + } else { + format!( + "Assertion failed: {escaped_expr_str}\nWith captures:\n{}", + self.fmt_string + ) + }), + suffix: None, }), - suffix: None, - }), - self.span, + self.span, + ), + Spacing::Joint, ), - TokenTree::token_alone(token::Comma, self.span), + (Token::new(token::Comma, self.span), Spacing::Alone), ]; let captures = self.capture_decls.iter().flat_map(|cap| { [ - TokenTree::token_joint( - token::Ident(cap.ident.name, IdentIsRaw::No), - cap.ident.span, + ( + Token::new(token::Ident(cap.ident.name, IdentIsRaw::No), cap.ident.span), + Spacing::Joint, ), - TokenTree::token_alone(token::Comma, self.span), + (Token::new(token::Comma, self.span), Spacing::Alone), ] }); self.cx.expr( @@ -178,7 +182,9 @@ impl<'cx, 'a> Context<'cx, 'a> { args: Box::new(DelimArgs { dspan: DelimSpan::from_single(self.span), delim: Delimiter::Parenthesis, - tokens: initial.into_iter().chain(captures).collect::(), + tokens: ArenaTokenStream::from_token_vec( + initial.into_iter().chain(captures).collect(), + ), }), })), ) diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 5a9988e076b0d..d79ea09d563e4 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -11,6 +11,7 @@ mod llvm_enzyme { DiffActivity, DiffMode, valid_input_activity, valid_ret_activity, valid_ty_for_activity, }; use rustc_ast::token::{Lit, LitKind, Token, TokenKind}; + use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::*; use rustc_ast::visit::AssocCtxt::*; use rustc_ast::{ @@ -153,12 +154,12 @@ mod llvm_enzyme { } } - fn meta_item_inner_to_ts(t: &MetaItemInner, ts: &mut Vec) { + fn meta_item_inner_to_ts(t: &MetaItemInner, ts: &mut Vec<(Token, Spacing)>) { let comma: Token = Token::new(TokenKind::Comma, Span::default()); let val = first_ident(t); let t = Token::from_ast_ident(val); - ts.push(TokenTree::Token(t, Spacing::Joint)); - ts.push(TokenTree::Token(comma, Spacing::Alone)); + ts.push((t, Spacing::Joint)); + ts.push((comma, Spacing::Alone)); } pub(crate) fn expand_forward( @@ -250,7 +251,7 @@ mod llvm_enzyme { // create TokenStream from vec elemtents: // meta_item doesn't have a .tokens field - let mut ts: Vec = vec![]; + let mut ts: Vec<(Token, Spacing)> = vec![]; if meta_item_vec.is_empty() { // At the bare minimum, we need a fnc name. dcx.emit_err(diagnostics::AutoDiffMissingConfig { span: item.span() }); @@ -265,11 +266,8 @@ mod llvm_enzyme { // Insert mode token let mode_token = Token::new(TokenKind::Ident(mode_symbol, false.into()), Span::default()); - ts.insert(0, TokenTree::Token(mode_token, Spacing::Joint)); - ts.insert( - 1, - TokenTree::Token(Token::new(TokenKind::Comma, Span::default()), Spacing::Alone), - ); + ts.insert(0, (mode_token, Spacing::Joint)); + ts.insert(1, (Token::new(TokenKind::Comma, Span::default()), Spacing::Alone)); // Now, if the user gave a width (vector aka batch-mode ad), then we copy it. // If it is not given, we default to 1 (scalar mode). @@ -289,8 +287,8 @@ mod llvm_enzyme { let l: Lit = Lit { kind, symbol, suffix: None }; let t = Token::new(TokenKind::Literal(l), Span::default()); let comma = Token::new(TokenKind::Comma, Span::default()); - ts.push(TokenTree::Token(t, Spacing::Joint)); - ts.push(TokenTree::Token(comma, Spacing::Alone)); + ts.push((t, Spacing::Joint)); + ts.push((comma, Spacing::Alone)); for t in meta_item_vec.clone()[start_position..].iter() { meta_item_inner_to_ts(t, &mut ts); @@ -300,12 +298,11 @@ mod llvm_enzyme { // We don't want users to provide a return activity if the function doesn't return anything. // For simplicity, we just add a dummy token to the end of the list. let t = Token::new(TokenKind::Ident(sym::None, false.into()), Span::default()); - ts.push(TokenTree::Token(t, Spacing::Joint)); - ts.push(TokenTree::Token(comma, Spacing::Alone)); + ts.push((t, Spacing::Joint)); + ts.push((comma, Spacing::Alone)); } // We remove the last, trailing comma. ts.pop(); - let ts: TokenStream = TokenStream::from_iter(ts); let x: RustcAutodiff = from_ast(ecx, &meta_item_vec, has_ret, mode); if !x.is_active() { @@ -345,14 +342,13 @@ mod llvm_enzyme { let mut rustc_ad_attr = Box::new(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_autodiff))); - let ts2: Vec = vec![TokenTree::Token( - Token::new(TokenKind::Ident(sym::never, false.into()), span), - Spacing::Joint, - )]; let never_arg = ast::DelimArgs { dspan: DelimSpan::from_single(span), delim: ast::token::Delimiter::Parenthesis, - tokens: TokenStream::from_iter(ts2), + tokens: ArenaTokenStream::from_token( + Token::new(TokenKind::Ident(sym::never, false.into()), span), + Spacing::Joint, + ), }; let inline_item = ast::AttrItem { unsafety: ast::Safety::Default, @@ -423,7 +419,7 @@ mod llvm_enzyme { rustc_ad_attr.item.args = rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs { dspan: DelimSpan::dummy(), delim: rustc_ast::token::Delimiter::Parenthesis, - tokens: ts, + tokens: ArenaTokenStream::from_token_vec(ts), }); let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 6a53dafd396df..01ce5b3c5b083 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -180,7 +180,8 @@ use std::{iter, vec}; pub(crate) use StaticFields::*; pub(crate) use SubstructureFields::*; use rustc_ast::token::{IdentIsRaw, LitKind, Token, TokenKind}; -use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenTree}; +use rustc_ast::tokenarena::ArenaTokenStream; +use rustc_ast::tokenstream::{DelimSpan, Spacing}; use rustc_ast::{ self as ast, AnonConst, AttrArgs, BindingMode, ByRef, DelimArgs, EnumDef, Expr, GenericArg, GenericParamKind, Generics, Mutability, PatKind, Safety, SelfKind, VariantData, @@ -806,20 +807,20 @@ impl<'a> TraitDef<'a> { args: AttrArgs::Delimited(DelimArgs { dspan: DelimSpan::from_single(self.span), delim: rustc_ast::token::Delimiter::Parenthesis, - tokens: [ - TokenKind::Ident(sym::feature, IdentIsRaw::No), - TokenKind::Eq, - TokenKind::lit(LitKind::Str, sym::derive_const, None), - TokenKind::Comma, - TokenKind::Ident(sym::issue, IdentIsRaw::No), - TokenKind::Eq, - TokenKind::lit(LitKind::Str, sym::derive_const_issue, None), - ] - .into_iter() - .map(|kind| { - TokenTree::Token(Token { kind, span: self.span }, Spacing::Alone) - }) - .collect(), + tokens: ArenaTokenStream::from_token_vec( + [ + TokenKind::Ident(sym::feature, IdentIsRaw::No), + TokenKind::Eq, + TokenKind::lit(LitKind::Str, sym::derive_const, None), + TokenKind::Comma, + TokenKind::Ident(sym::issue, IdentIsRaw::No), + TokenKind::Eq, + TokenKind::lit(LitKind::Str, sym::derive_const_issue, None), + ] + .into_iter() + .map(|kind| (Token { kind, span: self.span }, Spacing::Alone)) + .collect(), + ), }), span: self.span, }, diff --git a/compiler/rustc_builtin_macros/src/deriving/reborrow.rs b/compiler/rustc_builtin_macros/src/deriving/reborrow.rs index 9dc1ccf4fd8e6..e144d28770172 100644 --- a/compiler/rustc_builtin_macros/src/deriving/reborrow.rs +++ b/compiler/rustc_builtin_macros/src/deriving/reborrow.rs @@ -106,7 +106,7 @@ fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &Annotatable) -> Opt return None; } - let mut parser = cx.new_parser_from_tts(args.tokens.clone()); + let mut parser = cx.new_parser_from_tts(args.tokens.to_token_stream()); let target = match parser.parse_ty() { Ok(target) => target, Err(err) => { diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs index ac5c43c660088..3fa49a3921d25 100644 --- a/compiler/rustc_builtin_macros/src/edition_panic.rs +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -1,4 +1,5 @@ use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::*; use rustc_expand::base::*; @@ -59,7 +60,7 @@ fn expand<'cx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(sp), delim: Delimiter::Parenthesis, - tokens: tts, + tokens: ArenaTokenStream::from_stream(&tts), }), })), ), diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index 89fc222def5ff..dca20522562da 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -1,5 +1,6 @@ -use rustc_ast::token::{Delimiter, TokenKind}; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::token::{Delimiter, Token, TokenKind}; +use rustc_ast::tokenarena::{ArenaTokenStreamBuilder, DelimitedData}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing}; use rustc_ast::{ AttrKind, Attribute, DUMMY_NODE_ID, EiiDecl, EiiImpl, ItemKind, MetaItem, Mutability, Path, StmtKind, SyntheticAttr, Visibility, ast, @@ -498,21 +499,21 @@ fn generate_attribute_macro_to_implement( body: Box::new(ast::DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Brace, - tokens: TokenStream::from_iter([ - TokenTree::Delimited( - DelimSpan::from_single(span), - DelimSpacing::new(Spacing::Alone, Spacing::Alone), - Delimiter::Parenthesis, - TokenStream::default(), - ), - TokenTree::token_alone(TokenKind::FatArrow, span), - TokenTree::Delimited( - DelimSpan::from_single(span), - DelimSpacing::new(Spacing::Alone, Spacing::Alone), - Delimiter::Brace, - TokenStream::default(), - ), - ]), + tokens: { + let mut builder = ArenaTokenStreamBuilder::with_capacity(3); + builder.empty_delimited(DelimitedData { + span: DelimSpan::from_single(span), + spacing: DelimSpacing::new(Spacing::Alone, Spacing::Alone), + delimiter: Delimiter::Parenthesis, + }); + builder.push_token_alone(Token::new(TokenKind::FatArrow, span)); + builder.empty_delimited(DelimitedData { + span: DelimSpan::from_single(span), + spacing: DelimSpacing::new(Spacing::Alone, Spacing::Alone), + delimiter: Delimiter::Brace, + }); + builder.finish() + }, }), macro_rules: false, // #[eii_declaration(foreign_item_ident)] diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index 4111843b0c9d8..c8d170cc3e819 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -1,6 +1,7 @@ use rustc_ast::ast; use rustc_ast::token::{Delimiter, Token, TokenKind}; -use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenarena::ArenaTokenStream; +use rustc_ast::tokenstream::{DelimSpan, Spacing}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_session::config::Offload; use rustc_span::{DUMMY_SP, Ident, Span, sym}; @@ -125,7 +126,7 @@ pub(crate) fn expand_kernel( [sym::core, sym::unimplemented].map(|s| Ident::new(s, span)).to_vec(), ), Delimiter::Parenthesis, - TokenStream::default(), + ArenaTokenStream::default(), ), ); let stmt = ecx.stmt_expr(macro_expr); @@ -148,15 +149,13 @@ pub(crate) fn expand_kernel( } // inline(never) attr - let ts: Vec = vec![TokenTree::Token( - Token::new(TokenKind::Ident(sym::never, false.into()), span), - Spacing::Joint, - )]; - let never_arg = ast::DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Parenthesis, - tokens: TokenStream::from_iter(ts), + tokens: ArenaTokenStream::from_token( + Token::new(TokenKind::Ident(sym::never, false.into()), span), + Spacing::Joint, + ), }; let inline_item = ast::AttrItem { diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 2240fe115fde3..1545299f7a32e 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -1,5 +1,5 @@ use rustc_ast::token::Delimiter; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::util::literal; use rustc_ast::{ self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, @@ -56,7 +56,7 @@ impl<'a> ExtCtxt<'a> { span: Span, path: ast::Path, delim: Delimiter, - tokens: TokenStream, + tokens: ArenaTokenStream, ) -> Box { Box::new(ast::MacCall { path, @@ -486,7 +486,7 @@ impl<'a> ExtCtxt<'a> { [sym::std, sym::unreachable].map(|s| Ident::new(s, span)).to_vec(), ), Delimiter::Parenthesis, - TokenStream::default(), + ArenaTokenStream::default(), ), ) } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 586adc7698b93..811c2c1e9d2f2 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -725,7 +725,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ExpandResult::Ready(match invoc.kind { InvocationKind::Bang { mac, span } => { if let SyntaxExtensionKind::Bang(expander) = ext { - match expander.expand(self.cx, span, mac.args.tokens.clone()) { + match expander.expand(self.cx, span, mac.args.tokens.to_token_stream()) { Ok(tok_result) => { let fragment = self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span); @@ -743,16 +743,17 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } } else if let Some(expander) = ext.as_legacy_bang() { - let tok_result = match expander.expand(self.cx, span, mac.args.tokens.clone()) { - ExpandResult::Ready(tok_result) => tok_result, - ExpandResult::Retry(_) => { - // retry the original - return ExpandResult::Retry(Invocation { - kind: InvocationKind::Bang { mac, span }, - ..invoc - }); - } - }; + let tok_result = + match expander.expand(self.cx, span, mac.args.tokens.to_token_stream()) { + ExpandResult::Ready(tok_result) => tok_result, + ExpandResult::Retry(_) => { + // retry the original + return ExpandResult::Retry(Invocation { + kind: InvocationKind::Bang { mac, span }, + ..invoc + }); + } + }; if let Some(fragment) = fragment_kind.make_from(tok_result) { if macro_stats { update_bang_macro_stats(self.cx, fragment_kind, span, mac, &fragment); diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 7f35c9362be7e..00a987f70fb3b 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -797,7 +797,7 @@ pub fn compile_declarative_macro( let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; - let body = ArenaTokenStream::from_stream(¯o_def.body.tokens); + let body = macro_def.body.tokens.clone(); let mut p = Parser::new(&sess.psess, body, rustc_parse::MACRO_ARGUMENTS); // Don't abort iteration early, so that multiple errors can be reported. We only abort early on diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index ad6ae5481da39..3784ec1d25e7d 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -1,5 +1,6 @@ use rustc_ast::mut_visit::*; use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::visit::AssocCtxt; use rustc_ast::{self as ast}; use rustc_data_structures::fx::FxHashMap; @@ -20,7 +21,7 @@ pub(crate) fn placeholder( args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, - tokens: ast::tokenstream::TokenStream::new(Vec::new()), + tokens: ArenaTokenStream::default(), }), }) } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 6d1ae563a9fa2..59afae256bd58 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -153,7 +153,7 @@ impl<'a> State<'a> { None, *delim, None, - &tokens, + &tokens.to_token_stream(), true, span, ), diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index f85a14852d6cd..2078c550d92f6 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1826,10 +1826,10 @@ impl KeywordIdents { impl EarlyLintPass for KeywordIdents { fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef) { - self.check_tokens(cx, &mac_def.body.tokens); + self.check_tokens(cx, &mac_def.body.tokens.to_token_stream()); } fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { - self.check_tokens(cx, &mac.args.tokens); + self.check_tokens(cx, &mac.args.tokens.to_token_stream()); } fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: &Ident) { if ident.name.as_str().starts_with('\'') { diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs index 018b921a6b016..9419eee5df061 100644 --- a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs +++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs @@ -142,6 +142,6 @@ impl Expr2024 { impl EarlyLintPass for Expr2024 { fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) { - self.check_tokens(cx, &mc.body.tokens); + self.check_tokens(cx, &mc.body.tokens.to_token_stream()); } } diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 9d49fffb452cb..b9a62661bd788 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -30,7 +30,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { Ok(value) => value, Err(errs) => return Err(errs), }; - arena.finish_delimited(delimited, value); + arena.close_delimited(delimited, value); } 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. diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index edfaf6a9f8790..af2c37c619f10 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -348,7 +348,7 @@ fn fake_token_stream_for_file_mod( attr.span.between(spans.inner_span.shrink_to_hi()), &mut arena, )?; - arena.finish_delimited( + arena.close_delimited( start, DelimitedData { span: DelimSpan::from_single(semi.span), diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 260d0baf495f2..0fda5e5a3d060 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -5,8 +5,8 @@ use ast::token::IdentIsRaw; use rustc_ast as ast; use rustc_ast::ast::*; use rustc_ast::token::{self, Delimiter, MetaVarKind, TokenKind}; -use rustc_ast::tokenarena::ArenaTokenTree; -use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; +use rustc_ast::tokenstream::DelimSpan; use rustc_ast::util::case::Case; use rustc_ast_pretty::pprust; use rustc_errors::codes::*; @@ -2602,12 +2602,9 @@ impl<'a> Parser<'a> { let body = self.parse_token_tree(); // `MacBody` // Convert `MacParams MacBody` into `{ MacParams => MacBody }`. let bspan = body.span(); - let arrow = TokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` - let tokens = TokenStream::new(vec![ - params.to_token_tree(&self.token_cursor.stream), - arrow, - body.to_token_tree(&self.token_cursor.stream), - ]); + let arrow = ArenaTokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` + let tokens = + ArenaTokenStream::new_reparented(&[params, arrow, body], &self.token_cursor.stream); let dspan = DelimSpan::from_pair(pspan.shrink_to_lo(), bspan.shrink_to_hi()); Box::new(DelimArgs { dspan, delim: Delimiter::Brace, tokens }) } else { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 1f251ce8ed3a2..d179b5f56718c 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -723,21 +723,21 @@ impl<'a> Parser<'a> { fn check_const_closure(&self) -> bool { self.is_keyword_ahead(0, &[kw::Const]) && self.look_ahead(1, |t| match &t.kind { - // async closures do not work with const closures, so we do not parse that here. - token::Ident(kw::Move | kw::Use | kw::Static, IdentIsRaw::No) - | token::OrOr - | token::Or => true, - _ => false, - }) + // async closures do not work with const closures, so we do not parse that here. + token::Ident(kw::Move | kw::Use | kw::Static, IdentIsRaw::No) + | token::OrOr + | token::Or => true, + _ => false, + }) } fn check_inline_const(&self, dist: usize) -> bool { self.is_keyword_ahead(dist, &[kw::Const]) && self.look_ahead(dist + 1, |t| match &t.kind { - token::OpenBrace => true, - token::OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Block)) => true, - _ => false, - }) + token::OpenBrace => true, + token::OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Block)) => true, + _ => false, + }) } /// Checks to see if the next token is either `+` or `+=`. @@ -1241,7 +1241,7 @@ impl<'a> Parser<'a> { } else { None } - .map(|(kind, span)| CoroutineMarker::new(kind, span)) + .map(|(kind, span)| CoroutineMarker::new(kind, span)) } /// Parses fn unsafety: `unsafe`, `safe` or nothing. @@ -1386,12 +1386,14 @@ impl<'a> Parser<'a> { || self.check(exp!(OpenBrace)); delimited.then(|| { - let TokenTree::Delimited(dspan, _, delim, tokens) = - self.parse_token_tree().to_token_tree(&self.token_cursor.stream) - else { + let ArenaTokenTree::DelimitedStart(bounds, data) = self.parse_token_tree() else { unreachable!() }; - DelimArgs { dspan, delim, tokens } + DelimArgs { + dspan: data.span, + delim: data.delimiter, + tokens: ArenaTokenStream::separate_delimited_inner(bounds, &self.token_cursor.stream), + } }) } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 012c4997db9c1..e9cc246622fec 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -653,15 +653,16 @@ pub(super) fn display_macro_source(tcx: TyCtxt<'_>, name: Symbol, def: &ast::Mac if def.macro_rules { format!( "macro_rules! {name} {{\n{arms}}}", - arms = render_macro_arms(tcx, &def.body.tokens, ";") + arms = render_macro_arms(tcx, &def.body.tokens.to_token_stream(), ";") ) } else { - if def.body.tokens.len() <= 4 { + if def.body.tokens.to_token_stream().len() <= 4 { format!( "macro {name}{matchers} {{\n ...\n}}", matchers = def .body .tokens + .to_token_stream() .get(0) .map(|matcher| render_macro_matcher(tcx, matcher)) .unwrap_or_default(), @@ -669,7 +670,7 @@ pub(super) fn display_macro_source(tcx: TyCtxt<'_>, name: Symbol, def: &ast::Mac } else { format!( "macro {name} {{\n{arms}}}", - arms = render_macro_arms(tcx, &def.body.tokens, ",") + arms = render_macro_arms(tcx, &def.body.tokens.to_token_stream(), ",") ) } } diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 1fe62015b2c55..f471a0eeeeca5 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -614,7 +614,8 @@ fn parse_source( // in the macro input (!) to crudely detect main functions "masked by a // wrapper macro". For the record, this is a horrible heuristic! // See . - let mut iter = mac_call.mac.args.tokens.iter(); + let iter = mac_call.mac.args.tokens.to_token_stream(); + let mut iter = iter.iter(); while let Some(token) = iter.next() { if let TokenTree::Token(token, _) = token && let TokenKind::Ident(kw::Fn, _) = token.kind From e600fa68b25597e527de27f37a0b7b94ee14c982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 11 Sep 2026 18:19:40 +0200 Subject: [PATCH 14/25] Add `ArenaTokenTreeIter` and use it in `MetaItemKind::list_from_tokens` --- compiler/rustc_ast/src/attr/mod.rs | 91 +++++++++++++--------- compiler/rustc_ast/src/tokenarena.rs | 108 +++++++++++++++++---------- compiler/rustc_attr_ir/src/attr.rs | 2 +- 3 files changed, 124 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 12ed43d23c8dd..4385ed06ed7f8 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -19,10 +19,12 @@ use crate::ast::{ use crate::token::{ self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, }; -use crate::tokenarena::{ArenaTokenStream, ArenaTokenStreamBuilder}; +use crate::tokenarena::{ + ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, ArenaTokenTreeIter, DelimitedData, +}; use crate::tokenstream::{ AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, - TokenStream, TokenStreamIter, TokenTree, + TokenTree, }; use crate::util::comments; use crate::util::literal::escape_string_symbol; @@ -368,7 +370,7 @@ impl AttrItem { pub fn meta_item_list(&self) -> Option> { match &self.args { AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { - MetaItemKind::list_from_tokens(args.tokens.to_token_stream()) + MetaItemKind::list_from_tokens(&args.tokens) } AttrArgs::Delimited(_) | AttrArgs::Eq { .. } | AttrArgs::Empty => None, } @@ -498,16 +500,16 @@ impl MetaItem { } } - fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + fn from_tokens(iter: &mut ArenaTokenTreeIter<'_>) -> Option { // FIXME: Share code with `parse_path`. - let tt = iter.next().map(|tt| TokenTree::uninterpolate(tt)); + let tt = iter.next().map(|tt| ArenaTokenTree::uninterpolate(tt)); let path = match tt.as_deref() { - Some(&TokenTree::Token( + Some(&ArenaTokenTree::Token( Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span }, _, )) => 'arm: { let mut segments = if let &token::Ident(name, _) = kind { - if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = + if let Some(ArenaTokenTree::Token(Token { kind: token::PathSep, .. }, _)) = iter.peek() { iter.next(); @@ -519,13 +521,16 @@ impl MetaItem { thin_vec![PathSegment::path_root(span)] }; loop { - let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = - iter.next().map(|tt| TokenTree::uninterpolate(tt)).as_deref() + let Some(&ArenaTokenTree::Token( + Token { kind: token::Ident(name, _), span }, + _, + )) = iter.next().map(|tt| ArenaTokenTree::uninterpolate(tt)).as_deref() else { return None; }; segments.push(PathSegment::from_ident(Ident::new(name, span))); - let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = iter.peek() + let Some(ArenaTokenTree::Token(Token { kind: token::PathSep, .. }, _)) = + iter.peek() else { break; }; @@ -534,18 +539,21 @@ impl MetaItem { let span = span.with_hi(segments.last().unwrap().ident.span.hi()); Path { span, segments } } - Some(TokenTree::Delimited( - _span, - _spacing, - Delimiter::Invisible(InvisibleOrigin::MetaVar( - MetaVarKind::Meta { .. } | MetaVarKind::Path, - )), - _stream, + Some(ArenaTokenTree::DelimitedStart( + _, + DelimitedData { + delimiter: + Delimiter::Invisible(InvisibleOrigin::MetaVar( + MetaVarKind::Meta { .. } | MetaVarKind::Path, + )), + span: _, + spacing: _, + }, )) => { // This path is currently unreachable in the test suite. unreachable!() } - Some(TokenTree::Token(Token { kind, .. }, _)) if kind.is_delim() => { + Some(ArenaTokenTree::Token(Token { kind, .. }, _)) if kind.is_delim() => { panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tt); } _ => return None, @@ -567,41 +575,48 @@ impl MetaItem { impl MetaItemKind { // public because it can be called in the hir - pub fn list_from_tokens(tokens: TokenStream) -> Option> { - let mut iter = tokens.iter(); + pub fn list_from_tokens(tokens: &ArenaTokenStream) -> Option> { + let mut iter = tokens.iter_top_level_trees(); let mut result = ThinVec::new(); while iter.peek().is_some() { let item = MetaItemInner::from_tokens(&mut iter)?; result.push(item); match iter.next() { - None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => {} + None | Some(ArenaTokenTree::Token(Token { kind: token::Comma, .. }, _)) => {} _ => return None, } } Some(result) } - fn name_value_from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + fn name_value_from_tokens(iter: &mut ArenaTokenTreeIter<'_>) -> Option { match iter.next() { - Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { - MetaItemKind::name_value_from_tokens(&mut inner_tokens.iter()) - } - Some(TokenTree::Token(token, _)) => { + Some(ArenaTokenTree::DelimitedStart( + bounds, + DelimitedData { delimiter: Delimiter::Invisible(_), .. }, + )) => MetaItemKind::name_value_from_tokens(&mut iter.stream().iter_delimited(bounds)), + Some(ArenaTokenTree::Token(token, _)) => { MetaItemLit::from_token(token).map(MetaItemKind::NameValue) } _ => None, } } - fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + fn from_tokens(iter: &mut ArenaTokenTreeIter<'_>) -> Option { match iter.peek() { - Some(TokenTree::Delimited(.., Delimiter::Parenthesis, inner_tokens)) => { - let inner_tokens = inner_tokens.clone(); + Some(ArenaTokenTree::DelimitedStart( + bounds, + DelimitedData { delimiter: Delimiter::Parenthesis, .. }, + )) => { iter.next(); - MetaItemKind::list_from_tokens(inner_tokens).map(MetaItemKind::List) + MetaItemKind::list_from_tokens(&ArenaTokenStream::separate_delimited_inner( + *bounds, + iter.stream(), + )) + .map(MetaItemKind::List) } - Some(TokenTree::Delimited(..)) => None, - Some(TokenTree::Token(Token { kind: token::Eq, .. }, _)) => { + Some(ArenaTokenTree::DelimitedStart(..)) => None, + Some(ArenaTokenTree::Token(Token { kind: token::Eq, .. }, _)) => { iter.next(); MetaItemKind::name_value_from_tokens(iter) } @@ -613,7 +628,7 @@ impl MetaItemKind { match args { AttrArgs::Empty => Some(MetaItemKind::Word), AttrArgs::Delimited(DelimArgs { dspan: _, delim: Delimiter::Parenthesis, tokens }) => { - MetaItemKind::list_from_tokens(tokens.to_token_stream()).map(MetaItemKind::List) + MetaItemKind::list_from_tokens(tokens).map(MetaItemKind::List) } AttrArgs::Delimited(..) => None, AttrArgs::Eq { expr, .. } => match expr.kind { @@ -728,15 +743,17 @@ impl MetaItemInner { self.meta_item().is_some() } - fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + fn from_tokens(iter: &mut ArenaTokenTreeIter<'_>) -> Option { match iter.peek() { - Some(TokenTree::Token(token, _)) if let Some(lit) = MetaItemLit::from_token(token) => { + Some(ArenaTokenTree::Token(token, _)) + if let Some(lit) = MetaItemLit::from_token(token) => + { iter.next(); return Some(MetaItemInner::Lit(lit)); } - Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { + Some(ArenaTokenTree::DelimitedStart(bounds, _)) => { iter.next(); - return MetaItemInner::from_tokens(&mut inner_tokens.iter()); + return MetaItemInner::from_tokens(&mut iter.stream().iter_delimited(bounds)); } _ => {} } diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 0184ec49e056d..7c8d49506c3b2 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::sync::Arc; use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; @@ -30,6 +31,16 @@ impl ArenaTokenTree { ArenaTokenTree::Token(Token::new(kind, span), Spacing::Joint) } + pub fn uninterpolate(&self) -> Cow<'_, ArenaTokenTree> { + match self { + ArenaTokenTree::Token(token, spacing) => match token.uninterpolate() { + Cow::Owned(token) => Cow::Owned(ArenaTokenTree::Token(token, *spacing)), + Cow::Borrowed(_) => Cow::Borrowed(self), + }, + _ => Cow::Borrowed(self), + } + } + /// Convert an arena token tree to the tree-shaped token tree. pub fn to_token_tree(&self, arena: &ArenaTokenStream) -> TokenTree { match self { @@ -279,47 +290,16 @@ impl ArenaTokenStream { self.tokens.get(index) } - /// Iter top-level token trees of a delimited token sequence. - pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> impl Iterator { - let mut index = (bounds.start + 1) as usize; - let end = bounds.index_of_next_token_tree(); - std::iter::from_fn(move || { - if index >= end { - return None; - } - let item = self.get_innermost_elem_at(index)?; - match item { - token @ ArenaTokenTree::Token(..) => { - index += 1; - Some(*token) - } - tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { - index = bounds.index_of_next_token_tree(); - Some(*tree) - } - } - }) + /// Iterate top-level token trees of a delimited token sequence. + /// Does not return the delimited sequence start itself. + pub fn iter_delimited(&self, bounds: &DelimitedBounds) -> ArenaTokenTreeIter<'_> { + ArenaTokenTreeIter::new_delimited(self, bounds) } - pub fn iter_top_level_trees(&self) -> impl Iterator { - let mut index = 0; - let end = self.tokens.len(); - std::iter::from_fn(move || { - if index >= end { - return None; - } - let item = self.get_innermost_elem_at(index)?; - match item { - token @ ArenaTokenTree::Token(..) => { - index += 1; - Some(*token) - } - tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { - index = bounds.index_of_next_token_tree(); - Some(*tree) - } - } - }) + /// Iterate over the top-level token trees of the whole stream. + /// Does not recurse into delimited sequences. + pub fn iter_top_level_trees(&self) -> ArenaTokenTreeIter<'_> { + ArenaTokenTreeIter::new_top_level(self) } } @@ -329,6 +309,56 @@ impl StableHash for ArenaTokenStream { } } +pub struct ArenaTokenTreeIter<'a> { + index: usize, + end: usize, + stream: &'a ArenaTokenStream, +} + +impl<'a> ArenaTokenTreeIter<'a> { + fn new_top_level(stream: &'a ArenaTokenStream) -> Self { + Self { index: 0, end: stream.tokens.len(), stream } + } + + fn new_delimited(stream: &'a ArenaTokenStream, bounds: &DelimitedBounds) -> Self { + let index = (bounds.start + 1) as usize; + let end = bounds.index_of_next_token_tree(); + Self { index, end, stream } + } + + pub fn stream(&self) -> &'a ArenaTokenStream { + self.stream + } + + // 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<&'a ArenaTokenTree> { + self.stream.tokens.get(self.index) + } +} + +impl<'a> Iterator for ArenaTokenTreeIter<'a> { + type Item = &'a ArenaTokenTree; + + fn next(&mut self) -> Option { + if self.index >= self.end { + return None; + } + let item = self.stream.get_innermost_elem_at(self.index)?; + match item { + token @ ArenaTokenTree::Token(..) => { + self.index += 1; + Some(token) + } + tree @ ArenaTokenTree::DelimitedStart(bounds, _) => { + self.index = bounds.index_of_next_token_tree(); + Some(tree) + } + } + } +} + pub struct OpenDelimited { start: usize, } diff --git a/compiler/rustc_attr_ir/src/attr.rs b/compiler/rustc_attr_ir/src/attr.rs index 5126edd8d8cf5..51273b4ab4e44 100644 --- a/compiler/rustc_attr_ir/src/attr.rs +++ b/compiler/rustc_attr_ir/src/attr.rs @@ -154,7 +154,7 @@ impl AttributeExt for Attribute { match &self { Attribute::Unparsed(n) => match n.as_ref() { AttrItem { args: AttrArgs::Delimited(d), .. } => { - ast::MetaItemKind::list_from_tokens(d.tokens.to_token_stream()) + ast::MetaItemKind::list_from_tokens(&d.tokens) } _ => None, }, From 04cc54553cc767cfdbd6aa83e070b4f464181717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:00:08 +0200 Subject: [PATCH 15/25] Make `DelimitedBounds` fields private So that it can only be constructed in the `tokenarena` module. --- compiler/rustc_ast/src/tokenarena.rs | 10 +++++++--- compiler/rustc_ast/src/tokenstream.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 7c8d49506c3b2..00f7bb1f28da1 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -365,16 +365,20 @@ pub struct OpenDelimited { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub struct DelimitedBounds { - pub start: u32, + start: u32, /// The length includes both the start and the end token. /// So an empty delimited sequence has length 2. - pub length: u32, + length: u32, /// Index of the parent of the current delimited sequence. /// If this is the root delimited sequence, is `None`. - pub parent: Option, + parent: Option, } impl DelimitedBounds { + pub fn start(&self) -> usize { + self.start as usize + } + /// Return the index of the next token tree that follows this delimited token sequence. pub fn index_of_next_token_tree(&self) -> usize { (self.start + self.length) as usize diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 78c1190194d8f..314f8b3e24919 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -1002,7 +1002,7 @@ impl TokenCursor { fn get_delimited_data(&self, bounds: &DelimitedBounds) -> DelimitedData { let Some(ArenaTokenTree::DelimitedStart(_, data)) = - self.stream.get_innermost_elem_at(bounds.start as usize) + self.stream.get_innermost_elem_at(bounds.start()) else { panic!("Delimited sequence not found at the provided bounds"); }; From 93a251f78ea5bf1339ed2533dd411b5f26d38a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:15:44 +0200 Subject: [PATCH 16/25] Return `ArenaTokenStream` from `inner_tokens` --- compiler/rustc_ast/src/ast.rs | 10 +- compiler/rustc_ast/src/attr/mod.rs | 11 +- compiler/rustc_ast/src/tokenarena.rs | 163 +++++++++++++++++++++++++- compiler/rustc_ast/src/tokenstream.rs | 30 ++++- compiler/rustc_expand/src/expand.rs | 2 +- 5 files changed, 201 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 9191848aa2c26..d7faa8350ec6e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -38,7 +38,7 @@ use thin_vec::{ThinVec, thin_vec}; use crate::attr::data_structures::CfgEntry; pub use crate::format::*; use crate::token::{self, CommentKind, Delimiter}; -use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream}; +use crate::tokenstream::{DelimSpan, LazyAttrTokenStream}; use crate::util::parser::{ExprPrecedence, Fixity}; use crate::visit::{AssocCtxt, BoundKind, LifetimeCtxt}; @@ -2091,11 +2091,11 @@ impl AttrArgs { /// Tokens inside the delimiters or after `=`. /// Proc macros see these tokens, for example. - pub fn inner_tokens(&self) -> TokenStream { + pub fn inner_tokens(&self) -> ArenaTokenStream { match self { - AttrArgs::Empty => TokenStream::default(), - AttrArgs::Delimited(args) => args.tokens.to_token_stream(), - AttrArgs::Eq { expr, .. } => TokenStream::from_ast(expr), + AttrArgs::Empty => ArenaTokenStream::default(), + AttrArgs::Delimited(args) => args.tokens.clone(), + AttrArgs::Eq { expr, .. } => ArenaTokenStream::from_ast(expr), } } } diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 4385ed06ed7f8..dfc584f64f984 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -311,22 +311,19 @@ impl Attribute { } } - pub fn push_token_trees(&self, arena: &mut ArenaTokenStreamBuilder) { + pub fn push_token_trees(&self, builder: &mut ArenaTokenStreamBuilder) { match self.kind { AttrKind::Normal(ref normal) => { - for token_tree in normal + normal .tokens .as_ref() .unwrap_or_else(|| panic!("attribute is missing tokens: {self:?}")) .to_attr_token_stream() - .to_token_trees() - { - arena.push_token_tree(&token_tree); - } + .push_token_trees(builder); } // Empty tokens here ensures synthetic attributes are invisible to proc macros. AttrKind::Synthetic(..) => {} - AttrKind::DocComment(comment_kind, data) => arena.push_token_alone(Token::new( + AttrKind::DocComment(comment_kind, data) => builder.push_token_alone(Token::new( token::DocComment(comment_kind, self.style, data), self.span, )), diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index 00f7bb1f28da1..ac1d54c97fe55 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::fmt; use std::sync::Arc; use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; @@ -7,7 +8,10 @@ use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Span; use crate::token::{Delimiter, Token, TokenKind}; -use crate::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use crate::tokenstream::{ + DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenTree, +}; +use crate::{Attribute, HasTokens}; /// Part of a `TokenArena`. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] @@ -156,6 +160,62 @@ impl ArenaTokenStreamBuilder { self.close_delimited(start, delimited_data); } + /// Insert trees from `builder` at the start of a delimited sequence specified by + /// `bounds`. + pub fn insert_at_start_of_delimited( + &mut self, + bounds: DelimitedBounds, + builder: ArenaTokenStreamBuilder, + ) { + let start = bounds.start as usize; + let Some(ArenaTokenTree::DelimitedStart(..)) = self.get_innermost_elem_at(start) else { + panic!("insert_at_start_of_delimited called with invalid bounds"); + }; + // Insert the trees + let inserted_len = builder.tokens.len() as u32; + let after_insertion = bounds.start + inserted_len; + self.tokens.splice(start..start, builder.tokens); + + // Fix-up the start indices and parents after what was inserted + for tree in &mut self.tokens[after_insertion as usize..] { + match tree { + ArenaTokenTree::Token(_, _) => {} + ArenaTokenTree::DelimitedStart(b, _) => { + b.start += inserted_len; + b.parent = b.parent.map(|p| { + assert!(p >= start as u32); + if p > start as u32 { p + inserted_len } else { p } + }); + } + } + } + + // Fix-up the length of trees before what was inserted, including the current delimited + // sequence. + for tree in self.tokens[..start + 1].iter_mut().rev() { + match tree { + ArenaTokenTree::Token(_, _) => {} + ArenaTokenTree::DelimitedStart(b, _) => { + if b.index_of_next_token_tree() > start { + b.length += inserted_len; + } + } + } + } + + // Fix-up the start indices and parents in what was inserted + let offset = bounds.start + 1; + for tree in &mut self.tokens[start + 1..after_insertion as usize] { + match tree { + ArenaTokenTree::Token(_, _) => {} + ArenaTokenTree::DelimitedStart(b, _) => { + b.start += offset; + b.parent = b.parent.map(|p| p + offset); + } + } + } + } + pub fn get_innermost_elem_at(&self, index: usize) -> Option<&ArenaTokenTree> { self.tokens.get(index) } @@ -224,6 +284,13 @@ impl ArenaTokenStream { arena.finish() } + pub fn from_ast(node: &(impl HasTokens + fmt::Debug)) -> Self { + let tokens = node.tokens().unwrap_or_else(|| panic!("missing tokens for node: {:?}", node)); + let mut builder = ArenaTokenStreamBuilder::default(); + attrs_and_tokens_to_token_trees_arena(node.attrs(), tokens, &mut builder); + builder.finish() + } + pub fn to_token_stream(&self) -> TokenStream { let mut tokens = vec![]; for tt in self.iter_top_level_trees() { @@ -303,6 +370,100 @@ impl ArenaTokenStream { } } +// Converts multiple attributes and the tokens for a target AST node into token trees, and appends +// them to `res`. +// +// Example: if the AST node is "fn f() { blah(); }", then: +// - Simple if no attributes are present, e.g. "fn f() { blah(); }" +// - Simple if only outer attribute are present, e.g. "#[outer1] #[outer2] fn f() { blah(); }" +// - Trickier if inner attributes are present, because they must be moved within the AST node's +// tokens, e.g. "#[outer] fn f() { #![inner] blah() }" +pub fn attrs_and_tokens_to_token_trees_arena( + attrs: &[Attribute], + target_tokens: &LazyAttrTokenStream, + builder: &mut ArenaTokenStreamBuilder, +) { + let idx = attrs.partition_point(|attr| matches!(attr.style, crate::AttrStyle::Outer)); + let (outer_attrs, inner_attrs) = attrs.split_at(idx); + + // Add outer attribute tokens. + for attr in outer_attrs { + attr.push_token_trees(builder); + } + + // Add target AST node tokens. + target_tokens.to_attr_token_stream().push_token_trees(builder); + + // Insert inner attribute tokens. + if !inner_attrs.is_empty() { + if let Some(bounds) = get_insertion_point(inner_attrs, 0, builder.tokens.len(), builder) { + // FIXME: implement this in a more efficient way + let mut inner = ArenaTokenStreamBuilder::default(); + for attribute in inner_attrs { + attribute.push_token_trees(&mut inner); + } + builder.insert_at_start_of_delimited(bounds, inner); + } else { + panic!("Failed to find trailing delimited group in: {builder:?}"); + } + } + + // Inner attributes are only supported on blocks, functions, impls, and + // modules. All of these have their inner attributes placed at the + // beginning of the rightmost outermost braced group: + // e.g. `fn foo() { #![my_attr] }`. (Note: the braces may be within + // invisible delimiters.) + // + // Therefore, we can insert them back into the right location without + // needing to do any extra position tracking. + // + // Note: Outline modules are an exception - they can have attributes like + // `#![my_attr]` at the start of a file. Support for custom attributes in + // this position is not properly implemented - we always synthesize fake + // tokens, so we never reach this code. + fn get_insertion_point( + inner_attrs: &[Attribute], + start: usize, + end: usize, + builder: &ArenaTokenStreamBuilder, + ) -> Option { + let is_top_level = + |bounds: &DelimitedBounds| bounds.parent.map(|p| p < start as u32).unwrap_or(true); + + // We need to iterate backwards, only in the range given to us + for tree in builder.tokens[start..end].iter().rev() { + // We need to find only the "top-level" trees in the given range + // We recognize those by them either having no parent, or having a parent that is outside + // the range. + if let ArenaTokenTree::DelimitedStart( + bounds, + DelimitedData { delimiter: Delimiter::Brace, .. }, + ) = tree + { + if !is_top_level(bounds) { + continue; + } + // Found it: the rightmost, outermost braced group. + return Some(*bounds); + } else if let ArenaTokenTree::DelimitedStart( + bounds, + DelimitedData { delimiter: Delimiter::Invisible(_), .. }, + ) = tree + { + // Recurse inside invisible delimiters. + // We iterate from the first tree inside of this delimited sequence + let end = bounds.index_of_next_token_tree(); + if let Some(bounds) = + get_insertion_point(inner_attrs, bounds.start() + 1, end, builder) + { + return Some(bounds); + } + } + } + None + } +} + impl StableHash for ArenaTokenStream { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { self.tokens.as_slice().stable_hash(hcx, hasher); diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 314f8b3e24919..f5305b9a1456c 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -20,7 +20,10 @@ use thin_vec::ThinVec; use crate::ast::AttrStyle; use crate::ast_traits::HasTokens; use crate::token::{self, Delimiter, Token, TokenKind}; -use crate::tokenarena::{ArenaTokenStream, ArenaTokenTree, DelimitedBounds, DelimitedData}; +use crate::tokenarena::{ + ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, DelimitedBounds, DelimitedData, + attrs_and_tokens_to_token_trees_arena, +}; use crate::{AttrVec, Attribute}; #[cfg(test)] @@ -471,6 +474,31 @@ impl AttrTokenStream { } res } + + /// Converts this `AttrTokenStream` to a plain `Vec`. During + /// conversion, any `AttrTokenTree::AttrsTarget` gets "flattened" back to a + /// `TokenStream`, as described in the comment on + /// `attrs_and_tokens_to_token_trees`. + pub fn push_token_trees(&self, builder: &mut ArenaTokenStreamBuilder) { + for tree in self.0.iter() { + match tree { + AttrTokenTree::Token(inner, spacing) => { + builder.push_token(inner.clone(), *spacing); + } + AttrTokenTree::Delimited(span, spacing, delim, stream) => { + let start = builder.start_delimited(); + stream.push_token_trees(builder); + builder.close_delimited( + start, + DelimitedData { span: *span, spacing: *spacing, delimiter: *delim }, + ); + } + AttrTokenTree::AttrsTarget(target) => { + attrs_and_tokens_to_token_trees_arena(&target.attrs, &target.tokens, builder); + } + } + } + } } // Converts multiple attributes and the tokens for a target AST node into token trees, and appends diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 811c2c1e9d2f2..cf8dd72f8c0f9 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -838,7 +838,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if let AttrArgs::Eq { .. } = attr_item.args { self.cx.dcx().emit_err(UnsupportedKeyValue { span }); } - let inner_tokens = attr_item.args.inner_tokens(); + let inner_tokens = attr_item.args.inner_tokens().to_token_stream(); match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( From c3de20c5def610b1967dd39eb047a701bc1a2911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:19:31 +0200 Subject: [PATCH 17/25] Use `ArenaTokenStream` in builtin lints --- compiler/rustc_lint/src/builtin.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2078c550d92f6..30f0068e97b0f 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -17,7 +17,7 @@ use std::fmt::Write; use ast::token::TokenKind; use rustc_abi::BackendRepr; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenTree, ArenaTokenTreeIter}; use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::expr_to_string; @@ -1751,13 +1751,14 @@ declare_lint_pass!( struct UnderMacro(bool); impl KeywordIdents { - fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: &TokenStream) { + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: ArenaTokenTreeIter) { // Check if the preceding token is `$`, because we want to allow `$async`, etc. let mut prev_dollar = false; - for tt in tokens.iter() { + let stream = tokens.stream().clone(); + for tt in tokens { match tt { // Only report non-raw idents. - TokenTree::Token(token, _) => { + ArenaTokenTree::Token(token, _) => { if let Some((ident, token::IdentIsRaw::No)) = token.ident() { if !prev_dollar { self.check_ident_token(cx, UnderMacro(true), ident, ""); @@ -1774,7 +1775,9 @@ impl KeywordIdents { continue; } } - TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), + ArenaTokenTree::DelimitedStart(bounds, ..) => { + self.check_tokens(cx, stream.iter_delimited(bounds)) + } } prev_dollar = false; } @@ -1826,10 +1829,10 @@ impl KeywordIdents { impl EarlyLintPass for KeywordIdents { fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef) { - self.check_tokens(cx, &mac_def.body.tokens.to_token_stream()); + self.check_tokens(cx, mac_def.body.tokens.iter_top_level_trees()); } fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { - self.check_tokens(cx, &mac.args.tokens.to_token_stream()); + self.check_tokens(cx, mac.args.tokens.iter_top_level_trees()); } fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: &Ident) { if ident.name.as_str().starts_with('\'') { From b31d9b52c30422811b255d8cb13c737a36565620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:27:35 +0200 Subject: [PATCH 18/25] Use `ArenaTokenStream` in pretty printing --- compiler/rustc_ast_pretty/src/pprust/mod.rs | 7 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 80 +++++++++++-------- compiler/rustc_hir_pretty/src/lib.rs | 2 +- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs index 19bd8fd11bf2e..56570f9546c5a 100644 --- a/compiler/rustc_ast_pretty/src/pprust/mod.rs +++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs @@ -6,7 +6,8 @@ use std::borrow::Cow; use rustc_ast as ast; use rustc_ast::token::{Token, TokenKind}; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; +use rustc_ast::tokenstream::TokenStream; pub use state::{ AnnNode, Comments, PpAnn, PrintState, State, print_crate, print_crate_as_interface, }; @@ -44,8 +45,8 @@ pub fn expr_to_string(e: &ast::Expr) -> String { State::new().expr_to_string(e) } -pub fn tt_to_string(tt: &TokenTree) -> String { - State::new().tt_to_string(tt) +pub fn tt_to_string(tt: &ArenaTokenTree, stream: &ArenaTokenStream) -> String { + State::new().tt_to_string(tt, stream) } pub fn tts_to_string(tokens: &TokenStream) -> String { diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 040a53814343c..ed68505e05f4a 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -10,7 +10,8 @@ use std::borrow::Cow; use std::sync::Arc; use rustc_ast::attr::AttrIdGenerator; -use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree, DelimitedData}; +use rustc_ast::tokenstream::{Spacing, TokenStream}; use rustc_ast::util::classify; use rustc_ast::util::comments::{Comment, CommentStyle}; use rustc_ast::{ @@ -324,19 +325,22 @@ fn print_crate_inner<'a>( /// Returns `true` if both token trees are identifier-like tokens that would /// merge into a single token if printed without a space between them. /// E.g. `ident` + `where` would merge into `identwhere`. -fn idents_would_merge(tt1: &TokenTree, tt2: &TokenTree) -> bool { - fn is_ident_like(tt: &TokenTree) -> bool { - matches!(tt, TokenTree::Token(tk::Token { kind: tk::Ident(..) | tk::NtIdent(..), .. }, _,)) +fn idents_would_merge(tt1: &ArenaTokenTree, tt2: &ArenaTokenTree) -> bool { + fn is_ident_like(tt: &ArenaTokenTree) -> bool { + matches!( + tt, + ArenaTokenTree::Token(tk::Token { kind: tk::Ident(..) | tk::NtIdent(..), .. }, _,) + ) } is_ident_like(tt1) && is_ident_like(tt2) } -fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { - use TokenTree::{Delimited as Del, Token as Tok}; +fn space_between(tt1: &ArenaTokenTree, tt2: &ArenaTokenTree) -> bool { + use ArenaTokenTree::{DelimitedStart as Del, Token as Tok}; use tk::Delimiter::{Bracket, Parenthesis}; - fn is_punct(tt: &TokenTree) -> bool { - matches!(tt, TokenTree::Token(tok, _) if tok.is_punct()) + fn is_punct(tt: &ArenaTokenTree) -> bool { + matches!(tt, ArenaTokenTree::Token(tok, _) if tok.is_punct()) } // Each match arm has one or more examples in comments. The default is to @@ -372,18 +376,23 @@ fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { // IDENT|`fn`|`Self`|`pub` + `(`: `f(3)`, `fn(x: u8)`, `Self()`, `pub(crate)`, // but `let (a, b) = (1, 2)` needs a space after the `let` - (Tok(tk::Token { kind: tk::Ident(sym, is_raw), span }, _), Del(_, _, Parenthesis, _)) - if !Ident::new(*sym, *span).is_reserved() - || *sym == kw::Fn - || *sym == kw::SelfUpper - || *sym == kw::Pub - || matches!(is_raw, tk::IdentIsRaw::Yes) => + ( + Tok(tk::Token { kind: tk::Ident(sym, is_raw), span }, _), + Del(_, DelimitedData { delimiter: Parenthesis, .. }), + ) if !Ident::new(*sym, *span).is_reserved() + || *sym == kw::Fn + || *sym == kw::SelfUpper + || *sym == kw::Pub + || matches!(is_raw, tk::IdentIsRaw::Yes) => { false } // `#` + `[`: `#[attr]` - (Tok(tk::Token { kind: tk::Pound, .. }, _), Del(_, _, Bracket, _)) => false, + ( + Tok(tk::Token { kind: tk::Pound, .. }, _), + Del(_, DelimitedData { delimiter: Bracket, .. }), + ) => false, _ => true, } @@ -715,7 +724,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere None, *delim, None, - &tokens.to_token_stream(), + tokens, true, span, ), @@ -744,9 +753,14 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere /// appropriate macro, transcribe back into the grammar we just parsed from, /// and then pretty-print the resulting AST nodes (so, e.g., we print /// expression arguments as expressions). It can be done! I think. - fn print_tt(&mut self, tt: &TokenTree, convert_dollar_crate: bool) -> Spacing { + fn print_tt( + &mut self, + tt: &ArenaTokenTree, + stream: &ArenaTokenStream, + convert_dollar_crate: bool, + ) -> Spacing { match tt { - TokenTree::Token(token, spacing) => { + ArenaTokenTree::Token(token, spacing) => { let token_str = self.token_to_string_ext(token, convert_dollar_crate); self.word(token_str); // Emit hygiene annotations for identity-bearing tokens, @@ -771,18 +785,18 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } *spacing } - TokenTree::Delimited(dspan, spacing, delim, tts) => { + ArenaTokenTree::DelimitedStart(bounds, data) => { self.print_mac_common( None, false, None, - *delim, - Some(spacing.open), - tts, + data.delimiter, + Some(data.spacing.open), + &ArenaTokenStream::separate_delimited_inner(*bounds, stream), convert_dollar_crate, - dspan.entire(), + data.span.entire(), ); - spacing.close + data.spacing.close } } } @@ -816,10 +830,10 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere // output with simple string matching that can't handle whitespace changes. // E.g. we have seen cases where a proc macro can handle `a :: b` but not // `a::b`. See #117433 for some examples. - fn print_tts(&mut self, tts: &TokenStream, convert_dollar_crate: bool) { - let mut iter = tts.iter().peekable(); + fn print_tts(&mut self, tts: &ArenaTokenStream, convert_dollar_crate: bool) { + let mut iter = tts.iter_top_level_trees(); while let Some(tt) = iter.next() { - let spacing = self.print_tt(tt, convert_dollar_crate); + let spacing = self.print_tt(tt, tts, convert_dollar_crate); if let Some(next) = iter.peek() { if spacing == Spacing::Alone && space_between(tt, next) { self.space(); @@ -842,7 +856,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere ident: Option, delim: tk::Delimiter, open_spacing: Option, - tts: &TokenStream, + tts: &ArenaTokenStream, convert_dollar_crate: bool, span: Span, ) { @@ -926,7 +940,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Some(*ident), macro_def.body.delim, None, - ¯o_def.body.tokens.to_token_stream(), + ¯o_def.body.tokens, true, sp, ); @@ -1172,7 +1186,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } fn tts_to_string(&self, tokens: &TokenStream) -> String { - Self::to_string(|s| s.print_tts(tokens, false)) + Self::to_string(|s| s.print_tts(&ArenaTokenStream::from_stream(tokens), false)) } fn to_string(f: impl FnOnce(&mut State<'_>)) -> String { @@ -1674,7 +1688,7 @@ impl<'a> State<'a> { None, m.args.delim, None, - &m.args.tokens.to_token_stream(), + &m.args.tokens, true, m.span(), ); @@ -2410,9 +2424,9 @@ impl<'a> State<'a> { Self::to_string(|s| s.print_where_bound_predicate(where_bound_predicate)) } - pub(crate) fn tt_to_string(&self, tt: &TokenTree) -> String { + pub(crate) fn tt_to_string(&self, tt: &ArenaTokenTree, stream: &ArenaTokenStream) -> String { Self::to_string(|s| { - s.print_tt(tt, false); + s.print_tt(tt, stream, false); }) } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 59afae256bd58..5fb14c3a77c37 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -153,7 +153,7 @@ impl<'a> State<'a> { None, *delim, None, - &tokens.to_token_stream(), + tokens, true, span, ), From 499161ed54071d7d0796923d686f39353c82b617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:28:30 +0200 Subject: [PATCH 19/25] Use `ArenaTokenStream` in `MetaItemListParser` --- compiler/rustc_attr_parsing/src/parser.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index d3e029e52f99f..d5206fcfcb70a 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -15,7 +15,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use rustc_ast::token::{self, Delimiter, MetaVarKind}; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, PathSegment, StmtKind, UnOp, }; @@ -133,7 +132,7 @@ impl ArgParser { // Therefore we can substitute with a dummy value on invalid syntax. if matches!(parts, [sym::rustc_dummy] | [sym::diagnostic, ..]) { match MetaItemListParser::new( - &args.tokens.to_token_stream(), + args.tokens.clone(), args.dspan.entire(), psess, ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden }, @@ -164,7 +163,7 @@ impl ArgParser { Self::List( MetaItemListParser::new( - &args.tokens.to_token_stream(), + args.tokens.clone(), args.dspan.entire(), psess, should_emit, @@ -757,20 +756,14 @@ pub struct MetaItemListParser { } impl MetaItemListParser { - pub(crate) fn new<'sess>( - tokens: &TokenStream, + pub(crate) fn new( + tokens: ArenaTokenStream, span: Span, - psess: &'sess ParseSess, + psess: &ParseSess, should_emit: ShouldEmit, allow_expr_metavar: AllowExprMetavar, - ) -> Result> { - MetaItemListParserContext::parse( - ArenaTokenStream::from_stream(tokens), - psess, - span, - should_emit, - allow_expr_metavar, - ) + ) -> Result> { + MetaItemListParserContext::parse(tokens, psess, span, should_emit, allow_expr_metavar) } /// Lets you pick and choose as what you want to parse each element in the list From 232b42e778966c7e077217960cced6a796aa5ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 13:32:19 +0200 Subject: [PATCH 20/25] Use `ArenaTokenStream` in `new_parser_from_tts` --- compiler/rustc_builtin_macros/src/asm.rs | 7 +++++- compiler/rustc_builtin_macros/src/assert.rs | 2 +- compiler/rustc_builtin_macros/src/cfg.rs | 3 ++- .../rustc_builtin_macros/src/cfg_select.rs | 3 ++- .../src/deriving/reborrow.rs | 2 +- compiler/rustc_builtin_macros/src/format.rs | 3 ++- compiler/rustc_builtin_macros/src/iter.rs | 3 ++- .../rustc_builtin_macros/src/pattern_type.rs | 3 ++- .../src/test_binder_constraints.rs | 3 ++- compiler/rustc_builtin_macros/src/util.rs | 5 +++-- .../rustc_builtin_macros/src/view_type.rs | 3 ++- compiler/rustc_expand/src/base.rs | 4 ++-- compiler/rustc_expand/src/expand.rs | 22 +++++++++++++------ compiler/rustc_lint/src/builtin.rs | 2 +- 14 files changed, 43 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 5039d27a46fb4..b98f7c28e91c5 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -1,4 +1,5 @@ use rustc_ast as ast; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; @@ -32,7 +33,11 @@ fn parse_args<'a>( tts: TokenStream, asm_macro: AsmMacro, ) -> PResult<'a, ValidatedAsmArgs> { - let args = parse_asm_args(&mut ecx.new_parser_from_tts(tts), sp, asm_macro)?; + let args = parse_asm_args( + &mut ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)), + sp, + asm_macro, + )?; validate_asm_args(ecx, asm_macro, args) } diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index 6c6f4aa404d57..af47f347991e5 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -111,7 +111,7 @@ fn expr_if_not( } fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { - let mut parser = cx.new_parser_from_tts(stream); + let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); if parser.token == token::Eof { return Err(cx.dcx().create_err(diagnostics::AssertRequiresBoolean { span: sp })); diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index b34d928146efd..f4518de8706bb 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -2,6 +2,7 @@ //! a literal `true` or `false` based on whether the given cfg matches the //! current compilation environment. +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrStyle, token}; use rustc_attr_ir::target::Target; @@ -36,7 +37,7 @@ pub(crate) fn expand_cfg( } fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result { - let mut parser = cx.new_parser_from_tts(tts); + let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); if parser.token == token::Eof { return Err(cx.dcx().emit_err(diagnostics::RequiresCfgPattern { span })); } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index 69c3802ceafae..fc9edd7d1fe41 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,4 +1,5 @@ use rustc_ast::attr::AttrIdGenerator; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrKind, Expr, SyntheticAttr, ast}; use rustc_attr_ir::CfgEntry; @@ -122,7 +123,7 @@ pub(super) fn expand_cfg_select<'cx>( ) -> MacroExpanderResult<'cx> { ExpandResult::Ready( match parse_cfg_select( - &mut ecx.new_parser_from_tts(tts), + &mut ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)), ecx.sess, Some(ecx.ecfg.features), ecx.current_expansion.lint_node_id, diff --git a/compiler/rustc_builtin_macros/src/deriving/reborrow.rs b/compiler/rustc_builtin_macros/src/deriving/reborrow.rs index e144d28770172..9dc1ccf4fd8e6 100644 --- a/compiler/rustc_builtin_macros/src/deriving/reborrow.rs +++ b/compiler/rustc_builtin_macros/src/deriving/reborrow.rs @@ -106,7 +106,7 @@ fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &Annotatable) -> Opt return None; } - let mut parser = cx.new_parser_from_tts(args.tokens.to_token_stream()); + let mut parser = cx.new_parser_from_tts(args.tokens.clone()); let target = match parser.parse_ty() { Ok(target) => target, Err(err) => { diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 745de4d129766..7a17c5da5e42f 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -42,6 +42,7 @@ enum PositionUsedAs { Width, } use PositionUsedAs::*; +use rustc_ast::tokenarena::ArenaTokenStream; #[derive(Debug)] struct MacroInput { @@ -69,7 +70,7 @@ struct MacroInput { /// Ok((fmtstr, parsed arguments)) /// ``` fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { - let mut p = ecx.new_parser_from_tts(tts); + let mut p = ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); // parse the format string let fmtstr = match p.token.kind { diff --git a/compiler/rustc_builtin_macros/src/iter.rs b/compiler/rustc_builtin_macros/src/iter.rs index 86bf347cd9848..0e781220b6b91 100644 --- a/compiler/rustc_builtin_macros/src/iter.rs +++ b/compiler/rustc_builtin_macros/src/iter.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{CoroutineKind, CoroutineMarker, Expr, ast, token}; use rustc_errors::PResult; @@ -24,7 +25,7 @@ fn parse_closure<'a>( span: Span, stream: TokenStream, ) -> PResult<'a, Box> { - let mut closure_parser = cx.new_parser_from_tts(stream); + let mut closure_parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); let coroutine_marker = Some(CoroutineMarker::new(CoroutineKind::Gen, span)); diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs index 215baf099416b..7fcc4a002925e 100644 --- a/compiler/rustc_builtin_macros/src/pattern_type.rs +++ b/compiler/rustc_builtin_macros/src/pattern_type.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AnonConst, DUMMY_NODE_ID, Ty, TyPat, TyPatKind, ast, token}; use rustc_errors::PResult; @@ -25,7 +26,7 @@ fn parse_pat_ty<'a>( cx: &mut ExtCtxt<'a>, stream: TokenStream, ) -> PResult<'a, (Box, Box)> { - let mut parser = cx.new_parser_from_tts(stream); + let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); let ty = parser.parse_ty()?; parser.expect_keyword(exp!(Is))?; diff --git a/compiler/rustc_builtin_macros/src/test_binder_constraints.rs b/compiler/rustc_builtin_macros/src/test_binder_constraints.rs index 0c7482672df46..792300f01b60f 100644 --- a/compiler/rustc_builtin_macros/src/test_binder_constraints.rs +++ b/compiler/rustc_builtin_macros/src/test_binder_constraints.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrVec, VisibilityKind, ast, token}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; @@ -12,7 +13,7 @@ pub(crate) fn expand<'cx>( tts: TokenStream, ) -> MacroExpanderResult<'cx> { let name = "test_binder_constraints!"; - let mut p = cx.new_parser_from_tts(tts); + let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); if p.token == token::Eof { cx.dcx().emit_err(diagnostics::OnlyOneArgument { span, name }); }; diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 80fa3e3b8ac8d..c8de0df2dce97 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; use rustc_attr_parsing::{AttributeTemplate, validate_attr}; @@ -206,7 +207,7 @@ pub(crate) fn get_single_expr_from_tts( tts: TokenStream, name: &str, ) -> ExpandResult, ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(tts); + let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); if p.token == token::Eof { let guar = cx.dcx().emit_err(diagnostics::OnlyOneArgument { span, name }); return ExpandResult::Ready(Err(guar)); @@ -229,7 +230,7 @@ pub(crate) fn get_exprs_from_tts( cx: &mut ExtCtxt<'_>, tts: TokenStream, ) -> ExpandResult>, ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(tts); + let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); let mut es = Vec::new(); while p.token != token::Eof { let expr = match parse_expr(&mut p) { diff --git a/compiler/rustc_builtin_macros/src/view_type.rs b/compiler/rustc_builtin_macros/src/view_type.rs index 090603f4f1253..bfbac41715553 100644 --- a/compiler/rustc_builtin_macros/src/view_type.rs +++ b/compiler/rustc_builtin_macros/src/view_type.rs @@ -1,4 +1,5 @@ use rustc_ast::token::TokenKind; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{Ty, ast}; use rustc_errors::PResult; @@ -26,7 +27,7 @@ fn parse_view_ty<'a>( cx: &mut ExtCtxt<'a>, stream: TokenStream, ) -> PResult<'a, (Box, ThinVec)> { - let mut parser = cx.new_parser_from_tts(stream); + let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); let ty = parser.parse_ty()?; diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index def95be041f20..1ca89d6a0cc35 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1258,8 +1258,8 @@ impl<'a> ExtCtxt<'a> { pub fn monotonic_expander<'b>(&'b mut self) -> expand::MacroExpander<'b, 'a> { expand::MacroExpander::new(self, true) } - pub fn new_parser_from_tts(&self, stream: TokenStream) -> Parser<'a> { - Parser::new(&self.sess.psess, ArenaTokenStream::from_stream(&stream), MACRO_ARGUMENTS) + pub fn new_parser_from_tts(&self, stream: ArenaTokenStream) -> Parser<'a> { + Parser::new(&self.sess.psess, stream, MACRO_ARGUMENTS) } pub fn source_map(&self) -> &'a SourceMap { self.sess.psess.source_map() diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index cf8dd72f8c0f9..cd430c69f78dd 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::{iter, mem, slice}; use rustc_ast::mut_visit::*; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::visit::{AssocCtxt, Visitor, VisitorResult, try_visit, walk_list}; use rustc_ast::{ self as ast, AssocItemKind, AstNodeWrapper, AttrArgs, AttrKind, AttrStyle, AttrVec, @@ -727,8 +727,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if let SyntaxExtensionKind::Bang(expander) = ext { match expander.expand(self.cx, span, mac.args.tokens.to_token_stream()) { Ok(tok_result) => { - let fragment = - self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span); + let fragment = self.parse_ast_fragment( + ArenaTokenStream::from_stream(&tok_result), + fragment_kind, + &mac.path, + span, + ); if macro_stats { update_bang_macro_stats( self.cx, @@ -842,7 +846,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( - tok_result, + ArenaTokenStream::from_stream(&tok_result), fragment_kind, &attr_item.path, span, @@ -963,8 +967,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { let body = item.to_tokens(); match expander.expand_derive(self.cx, span, &body) { Ok(tok_result) => { - let fragment = - self.parse_ast_fragment(tok_result, fragment_kind, &path, span); + let fragment = self.parse_ast_fragment( + ArenaTokenStream::from_stream(&tok_result), + fragment_kind, + &path, + span, + ); if macro_stats { update_derive_macro_stats( self.cx, @@ -1058,7 +1066,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fn parse_ast_fragment( &mut self, - toks: TokenStream, + toks: ArenaTokenStream, kind: AstFragmentKind, path: &ast::Path, span: Span, diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 30f0068e97b0f..12a31d30d6fe5 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1751,7 +1751,7 @@ declare_lint_pass!( struct UnderMacro(bool); impl KeywordIdents { - fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: ArenaTokenTreeIter) { + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: ArenaTokenTreeIter<'_>) { // Check if the preceding token is `$`, because we want to allow `$async`, etc. let mut prev_dollar = false; let stream = tokens.stream().clone(); From 15af75d4d53609e82b7a0280d2b73197dc048baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 14:35:26 +0200 Subject: [PATCH 21/25] Use `ArenaTokenStream` in some parts of macro expansion --- compiler/rustc_ast/src/tokenarena.rs | 49 +++++++ compiler/rustc_ast_pretty/src/pprust/mod.rs | 3 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 6 +- compiler/rustc_builtin_macros/src/cfg_eval.rs | 4 +- .../rustc_builtin_macros/src/contracts.rs | 19 +-- .../rustc_builtin_macros/src/log_syntax.rs | 3 +- .../rustc_builtin_macros/src/source_util.rs | 3 +- compiler/rustc_expand/src/base.rs | 40 +++--- compiler/rustc_expand/src/expand.rs | 30 ++-- compiler/rustc_expand/src/mbe/diagnostics.rs | 50 ++++++- compiler/rustc_expand/src/mbe/macro_rules.rs | 130 ++++++++++++++---- compiler/rustc_expand/src/proc_macro.rs | 44 +++--- .../rustc_expand/src/proc_macro_server.rs | 2 +- compiler/rustc_expand_queries/src/derive.rs | 10 +- 14 files changed, 285 insertions(+), 108 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index ac1d54c97fe55..ee593a8c64bd5 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -160,6 +160,48 @@ impl ArenaTokenStreamBuilder { self.close_delimited(start, delimited_data); } + /// Copy `stream` into this builder, while possibly adding additional tokens or skipping + /// existing tokens. + pub fn build_from_stream(&mut self, stream: &ArenaTokenStream, mut func: F) + where + F: FnMut(&mut Self, &ArenaTokenTree) -> PerTreeOp, + { + fn fill( + builder: &mut ArenaTokenStreamBuilder, + func: &mut dyn FnMut(&mut ArenaTokenStreamBuilder, &ArenaTokenTree) -> PerTreeOp, + tree: &ArenaTokenTree, + stream: &ArenaTokenStream, + ) { + match func(builder, tree) { + PerTreeOp::Continue => {} + PerTreeOp::Skip => { + return; + } + } + match tree { + ArenaTokenTree::Token(token, spacing) => { + builder.push_token(*token, *spacing); + } + ArenaTokenTree::DelimitedStart(bounds, data) => { + let start = builder.start_delimited(); + fill_iter(builder, func, stream.iter_delimited(bounds)); + builder.close_delimited(start, *data); + } + } + } + fn fill_iter( + builder: &mut ArenaTokenStreamBuilder, + func: &mut dyn FnMut(&mut ArenaTokenStreamBuilder, &ArenaTokenTree) -> PerTreeOp, + iter: ArenaTokenTreeIter<'_>, + ) { + let stream = iter.stream().clone(); + for tree in iter { + fill(builder, func, tree, &stream); + } + } + fill_iter(self, &mut func, stream.iter_top_level_trees()); + } + /// Insert trees from `builder` at the start of a delimited sequence specified by /// `bounds`. pub fn insert_at_start_of_delimited( @@ -235,6 +277,13 @@ impl ArenaTokenStreamBuilder { } } +pub enum PerTreeOp { + /// Continue processing the tree as normally. + Continue, + /// Skip the tree, do not insert it. + Skip, +} + #[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] pub struct ArenaTokenStream { tokens: Arc>, diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs index 56570f9546c5a..ced2c822a9752 100644 --- a/compiler/rustc_ast_pretty/src/pprust/mod.rs +++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs @@ -7,7 +7,6 @@ use std::borrow::Cow; use rustc_ast as ast; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; -use rustc_ast::tokenstream::TokenStream; pub use state::{ AnnNode, Comments, PpAnn, PrintState, State, print_crate, print_crate_as_interface, }; @@ -49,7 +48,7 @@ pub fn tt_to_string(tt: &ArenaTokenTree, stream: &ArenaTokenStream) -> String { State::new().tt_to_string(tt, stream) } -pub fn tts_to_string(tokens: &TokenStream) -> String { +pub fn tts_to_string(tokens: &ArenaTokenStream) -> String { State::new().tts_to_string(tokens) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index ed68505e05f4a..ed9db88fbd209 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use rustc_ast::attr::AttrIdGenerator; use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree, DelimitedData}; -use rustc_ast::tokenstream::{Spacing, TokenStream}; +use rustc_ast::tokenstream::Spacing; use rustc_ast::util::classify; use rustc_ast::util::comments::{Comment, CommentStyle}; use rustc_ast::{ @@ -1185,8 +1185,8 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Self::to_string(|s| s.print_attr_item(ai, ai.path.span)) } - fn tts_to_string(&self, tokens: &TokenStream) -> String { - Self::to_string(|s| s.print_tts(&ArenaTokenStream::from_stream(tokens), false)) + fn tts_to_string(&self, stream: &ArenaTokenStream) -> String { + Self::to_string(|s| s.print_tts(stream, false)) } fn to_string(f: impl FnOnce(&mut State<'_>)) -> String { diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 8ad47ea566a0e..34ddd9427cdde 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -2,7 +2,6 @@ use core::ops::ControlFlow; use rustc_ast as ast; use rustc_ast::mut_visit::MutVisitor; -use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{Attribute, HasTokens, NodeId, mut_visit, visit}; use rustc_errors::PResult; @@ -106,8 +105,7 @@ impl CfgEval<'_> { // // After that we have our re-parsed `AttrTokenStream`, recursively configuring // our attribute target will correctly configure the tokens as well. - let mut parser = - Parser::new(&self.0.sess.psess, ArenaTokenStream::from_stream(&orig_tokens), None); + let mut parser = Parser::new(&self.0.sess.psess, orig_tokens, None); parser.capture_cfg = true; let res: PResult<'_, Option> = try { match &annotatable { diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index 20001400857a6..65762e7b78e7d 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -1,4 +1,5 @@ use rustc_ast::token; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_errors::ErrorGuaranteed; use rustc_expand::base::{AttrProcMacro, ExtCtxt}; @@ -14,8 +15,8 @@ impl AttrProcMacro for ExpandRequires { &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result { expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractRequires) } @@ -26,8 +27,8 @@ impl AttrProcMacro for ExpandEnsures { &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result { expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractEnsures) } @@ -133,8 +134,8 @@ fn expand_contract_clause( fn expand_contract_clause_tts( ecx: &mut ExtCtxt<'_>, attr_span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, clause_keyword: rustc_span::Symbol, ) -> Result { if annotation.is_empty() { @@ -149,11 +150,11 @@ fn expand_contract_clause_tts( ); // Returning `Err` would replace it with a dummy fragment and cause cascading name-resolution errors. // Instead, we return the original token stream so that there is no later noises. - return Ok(annotated); + return Ok(annotated.to_token_stream()); } let feature_span = ecx.with_def_site_ctxt(attr_span); - expand_contract_clause(ecx, attr_span, annotated, |new_tts| { + expand_contract_clause(ecx, attr_span, annotated.to_token_stream(), |new_tts| { new_tts.push(TokenTree::Token( token::Token::from_ast_ident(Ident::new(clause_keyword, feature_span)), Spacing::Joint, @@ -162,7 +163,7 @@ fn expand_contract_clause_tts( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), token::Delimiter::Brace, - annotation, + annotation.to_token_stream(), )); Ok(()) }) diff --git a/compiler/rustc_builtin_macros/src/log_syntax.rs b/compiler/rustc_builtin_macros/src/log_syntax.rs index 205f21ae7c9d3..c003dbab17e0f 100644 --- a/compiler/rustc_builtin_macros/src/log_syntax.rs +++ b/compiler/rustc_builtin_macros/src/log_syntax.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast_pretty::pprust; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; @@ -7,7 +8,7 @@ pub(crate) fn expand_log_syntax<'cx>( sp: rustc_span::Span, tts: TokenStream, ) -> MacroExpanderResult<'cx> { - println!("{}", pprust::tts_to_string(&tts)); + println!("{}", pprust::tts_to_string(&ArenaTokenStream::from_stream(&tts))); // any so that `log_syntax` can be invoked as an expression and item. ExpandResult::Ready(DummyResult::any_valid(sp)) diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index 37b2f49c3596d..fd2da4260904f 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast as ast; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{join_path_idents, token}; use rustc_ast_pretty::pprust; @@ -82,7 +83,7 @@ pub(crate) fn expand_stringify( tts: TokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - let s = pprust::tts_to_string(&tts); + let s = pprust::tts_to_string(&ArenaTokenStream::from_stream(&tts)); ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s)))) } diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 1ca89d6a0cc35..c94b824021464 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -118,16 +118,16 @@ impl Annotatable { } /// Converts the `Annotatable` to a token stream, e.g. to hand to a proc macro. - pub fn to_tokens(&self) -> TokenStream { + pub fn to_tokens(&self) -> ArenaTokenStream { match self { - Annotatable::Item(node) => TokenStream::from_ast(node), - Annotatable::AssocItem(node, _) => TokenStream::from_ast(node), - Annotatable::ForeignItem(node) => TokenStream::from_ast(node), + Annotatable::Item(node) => ArenaTokenStream::from_ast(node), + Annotatable::AssocItem(node, _) => ArenaTokenStream::from_ast(node), + Annotatable::ForeignItem(node) => ArenaTokenStream::from_ast(node), Annotatable::Stmt(node) => { assert!(!matches!(node.kind, ast::StmtKind::Empty)); - TokenStream::from_ast(node) + ArenaTokenStream::from_ast(node) } - Annotatable::Expr(node) => TokenStream::from_ast(node), + Annotatable::Expr(node) => ArenaTokenStream::from_ast(node), Annotatable::Arm(..) | Annotatable::ExprField(..) | Annotatable::PatField(..) @@ -316,19 +316,19 @@ pub trait BangProcMacro { &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - ts: TokenStream, + ts: ArenaTokenStream, ) -> Result; } impl BangProcMacro for F where - F: Fn(&mut ExtCtxt<'_>, Span, TokenStream) -> Result, + F: Fn(&mut ExtCtxt<'_>, Span, ArenaTokenStream) -> Result, { fn expand<'cx>( &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - ts: TokenStream, + ts: ArenaTokenStream, ) -> Result { // FIXME setup implicit context in TLS before calling self. self(ecx, span, ts) @@ -340,8 +340,8 @@ pub trait AttrProcMacro { &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result; // Default implementation for safe attributes; override if the attribute can be unsafe. @@ -350,8 +350,8 @@ pub trait AttrProcMacro { ecx: &'cx mut ExtCtxt<'_>, safety: Safety, span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result { if let Safety::Unsafe(span) = safety { ecx.dcx().span_err(span, "unnecessary `unsafe` on safe attribute"); @@ -362,14 +362,14 @@ pub trait AttrProcMacro { impl AttrProcMacro for F where - F: Fn(TokenStream, TokenStream) -> TokenStream, + F: Fn(ArenaTokenStream, ArenaTokenStream) -> TokenStream, { fn expand<'cx>( &self, _ecx: &'cx mut ExtCtxt<'_>, _span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result { // FIXME setup implicit context in TLS before calling self. Ok(self(annotation, annotated)) @@ -382,7 +382,7 @@ pub trait TTMacroExpander: Any { &'a self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - input: TokenStream, + input: ArenaTokenStream, ) -> MacroExpanderResult<'cx>; } @@ -399,9 +399,9 @@ where &'a self, ecx: &'cx mut ExtCtxt<'_>, span: Span, - input: TokenStream, + input: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { - self(ecx, span, input) + self(ecx, span, input.to_token_stream()) } } @@ -935,7 +935,7 @@ impl SyntaxExtension { fn expand( ecx: &mut ExtCtxt<'_>, span: Span, - _ts: TokenStream, + _ts: ArenaTokenStream, ) -> Result { Err(ecx.dcx().span_delayed_bug(span, "expanded a dummy bang macro")) } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index cd430c69f78dd..d5513030ebec5 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -725,7 +725,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ExpandResult::Ready(match invoc.kind { InvocationKind::Bang { mac, span } => { if let SyntaxExtensionKind::Bang(expander) = ext { - match expander.expand(self.cx, span, mac.args.tokens.to_token_stream()) { + match expander.expand(self.cx, span, mac.args.tokens.clone()) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( ArenaTokenStream::from_stream(&tok_result), @@ -747,17 +747,16 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } } else if let Some(expander) = ext.as_legacy_bang() { - let tok_result = - match expander.expand(self.cx, span, mac.args.tokens.to_token_stream()) { - ExpandResult::Ready(tok_result) => tok_result, - ExpandResult::Retry(_) => { - // retry the original - return ExpandResult::Retry(Invocation { - kind: InvocationKind::Bang { mac, span }, - ..invoc - }); - } - }; + let tok_result = match expander.expand(self.cx, span, mac.args.tokens.clone()) { + ExpandResult::Ready(tok_result) => tok_result, + ExpandResult::Retry(_) => { + // retry the original + return ExpandResult::Retry(Invocation { + kind: InvocationKind::Bang { mac, span }, + ..invoc + }); + } + }; if let Some(fragment) = fragment_kind.make_from(tok_result) { if macro_stats { update_bang_macro_stats(self.cx, fragment_kind, span, mac, &fragment); @@ -782,7 +781,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { // we are invoking it on an out-of-line module or crate. Annotatable::Crate(krate) => { rustc_parse::fake_token_stream_for_crate(&self.cx.sess.psess, krate) - .to_token_stream() } Annotatable::Item(item_inner) if matches!(attr.style, AttrStyle::Inner) @@ -801,7 +799,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, Some(&attr), ) - .to_token_stream() } Annotatable::Item(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_item( @@ -809,7 +806,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, None, ) - .to_token_stream() } // When a function has EII implementations attached (via `eii_impl`), // use fake tokens so the pretty-printer re-emits the EII attribute @@ -826,14 +822,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item_inner, None, ) - .to_token_stream() } Annotatable::ForeignItem(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_foreign_item( &self.cx.sess.psess, item_inner, ) - .to_token_stream() } _ => item.to_tokens(), }; @@ -842,7 +836,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if let AttrArgs::Eq { .. } = attr_item.args { self.cx.dcx().emit_err(UnsupportedKeyValue { span }); } - let inner_tokens = attr_item.args.inner_tokens().to_token_stream(); + let inner_tokens = attr_item.args.inner_tokens(); match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 024a0542a5f64..dce0b2e682eb8 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -1,7 +1,8 @@ use std::borrow::Cow; use rustc_ast::token::{self, Token}; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, PerTreeOp}; +use rustc_ast::tokenstream::Spacing; use rustc_attr_ir::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize}; @@ -22,7 +23,7 @@ use crate::mbe::macro_rules::{ pub(super) enum FailedMacro<'a> { Func, - Attr(&'a TokenStream), + Attr(&'a ArenaTokenStream), Derive, } @@ -32,7 +33,7 @@ pub(super) fn failed_to_match_macro( def_span: Span, name: Ident, args: FailedMacro<'_>, - body: &TokenStream, + body: &ArenaTokenStream, rules: &[MacroRule], on_unmatched_args: Option<&Directive>, ) -> (Span, ErrorGuaranteed) { @@ -48,7 +49,7 @@ pub(super) fn failed_to_match_macro( let mut tracker = CollectTrackerAndEmitter::new(name, psess.dcx(), sp); let try_success_result = match args { - FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker), + FailedMacro::Func => try_match_macro(psess, name, &body, rules, &mut tracker), FailedMacro::Attr(attr_args) => { try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker) } @@ -117,7 +118,7 @@ pub(super) fn failed_to_match_macro( // Check whether there's a missing comma in this macro call, like `println!("{}" a);` if let FailedMacro::Func = args - && let Some((body, comma_span)) = body.add_comma() + && let Some((body, comma_span)) = add_comma(body) { for rule in rules { let MacroRule::Func { lhs, .. } = rule else { continue }; @@ -144,6 +145,45 @@ pub(super) fn failed_to_match_macro( (sp, guar) } +/// Given an `ArenaTokenStream` with a `Stream` of only two arguments, return a new `ArenaTokenStream` +/// separating the two arguments with a comma for diagnostic suggestions. +fn add_comma(stream: &ArenaTokenStream) -> Option<(ArenaTokenStream, Span)> { + // Used to suggest if a user writes `foo!(a b);` + let mut suggestion = None; + let mut iter = stream.iter_top_level_trees().enumerate().peekable(); + while let Some((pos, ts)) = iter.next() { + if let Some((_, next)) = iter.peek() { + let sp = match (&ts, &next) { + (_, ArenaTokenTree::Token(Token { kind: token::Comma, .. }, _)) => continue, + ( + ArenaTokenTree::Token(token_left, Spacing::Alone), + ArenaTokenTree::Token(token_right, _), + ) if (token_left.is_non_reserved_ident() || token_left.is_lit()) + && (token_right.is_non_reserved_ident() || token_right.is_lit()) => + { + token_left.span + } + (ArenaTokenTree::DelimitedStart(_, data), _) => data.span.entire(), + _ => continue, + }; + let sp = sp.shrink_to_hi(); + let comma = Token::new(token::Comma, sp); + suggestion = Some((pos, comma, sp)); + } + } + if let Some((pos, token, sp)) = suggestion { + let mut builder = ArenaTokenStreamBuilder::with_capacity(stream.length() + 1); + builder.build_from_stream(stream, |builder, _| { + if builder.length() == pos + 1 { + builder.push_token(token, Spacing::Alone); + } + PerTreeOp::Continue + }); + return Some((builder.finish(), sp)); + } + None +} + /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { macro_name: Ident, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 00a987f70fb3b..ef4b701862cf8 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -1,15 +1,18 @@ use std::borrow::Cow; use std::collections::hash_map::Entry; use std::sync::Arc; -use std::{mem, slice}; +use std::{cmp, mem, slice}; 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::tokenarena::{ArenaTokenStream, DelimitedBounds, DelimitedData}; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; -use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId, Safety}; +use rustc_ast::tokenarena::{ + ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, DelimitedBounds, DelimitedData, + PerTreeOp, +}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream}; +use rustc_ast::{self as ast, AttrStyle, DUMMY_NODE_ID, NodeId, Safety}; use rustc_ast_pretty::pprust; use rustc_attr_ir::diagnostic::Directive; use rustc_attr_ir::{self as attrs, find_attr}; @@ -232,7 +235,7 @@ impl MacroRulesMacroExpander { &self, cx: &mut ExtCtxt<'_>, sp: Span, - body: &TokenStream, + body: &ArenaTokenStream, ) -> Result { // This is similar to `expand_macro`, but they have very different signatures, and will // diverge further once derives support arguments. @@ -259,7 +262,10 @@ impl MacroRulesMacroExpander { .map_err(|e| e.emit())?; if cx.trace_macros() { - let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + let msg = format!( + "to `{}`", + pprust::tts_to_string(&ArenaTokenStream::from_stream(&tts)) + ); trace_macros_note(&mut cx.expansions, sp, msg); } @@ -293,7 +299,7 @@ impl TTMacroExpander for MacroRulesMacroExpander { &'a self, cx: &'cx mut ExtCtxt<'_>, sp: Span, - input: TokenStream, + input: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { ExpandResult::Ready(expand_macro( cx, @@ -314,8 +320,8 @@ impl AttrProcMacro for MacroRulesMacroExpander { &self, _cx: &mut ExtCtxt<'_>, _sp: Span, - _args: TokenStream, - _body: TokenStream, + _args: ArenaTokenStream, + _body: ArenaTokenStream, ) -> Result { unreachable!("`expand` called on `MacroRulesMacroExpander`, expected `expand_with_safety`") } @@ -325,8 +331,8 @@ impl AttrProcMacro for MacroRulesMacroExpander { cx: &mut ExtCtxt<'_>, safety: Safety, sp: Span, - args: TokenStream, - body: TokenStream, + args: ArenaTokenStream, + body: ArenaTokenStream, ) -> Result { expand_macro_attr( cx, @@ -351,7 +357,7 @@ impl BangProcMacro for DummyBang { &self, _: &'cx mut ExtCtxt<'_>, _: Span, - _: TokenStream, + _: ArenaTokenStream, ) -> Result { Err(self.0) } @@ -436,7 +442,7 @@ fn expand_macro<'cx, 'a: 'cx>( node_id: NodeId, name: Ident, transparency: Transparency, - arg: TokenStream, + arg: ArenaTokenStream, rules: &'a [MacroRule], on_unmatched_args: Option<&Directive>, ) -> Box { @@ -471,7 +477,8 @@ 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(&ArenaTokenStream::from_stream(&tts))); trace_macros_note(&mut cx.expansions, sp, msg); } @@ -515,8 +522,8 @@ fn expand_macro_attr( name: Ident, transparency: Transparency, safety: Safety, - args: TokenStream, - body: TokenStream, + args: ArenaTokenStream, + body: ArenaTokenStream, rules: &[MacroRule], on_unmatched_args: Option<&Directive>, ) -> Result { @@ -564,6 +571,7 @@ 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())?; + let tts = ArenaTokenStream::from_stream(&tts); if cx.trace_macros() { let msg = format!("to `{}`", pprust::tts_to_string(&tts)); @@ -574,7 +582,7 @@ fn expand_macro_attr( cx.resolver.record_macro_rule_usage(node_id, i); } - Ok(tts) + Ok(tts.to_token_stream()) } Err(CanRetry::No(guar)) => Err(guar), Err(CanRetry::Yes) => { @@ -608,7 +616,7 @@ pub(super) enum CanRetry { pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( psess: &ParseSess, name: Ident, - arg: &TokenStream, + arg: &ArenaTokenStream, rules: &'matcher [MacroRule], track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { @@ -688,8 +696,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( psess: &ParseSess, name: Ident, - attr_args: &TokenStream, - attr_body: &TokenStream, + attr_args: &ArenaTokenStream, + attr_body: &ArenaTokenStream, rules: &'matcher [MacroRule], track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { @@ -745,7 +753,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( psess: &ParseSess, name: Ident, - body: &TokenStream, + body: &ArenaTokenStream, rules: &'matcher [MacroRule], track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { @@ -1882,10 +1890,82 @@ fn is_defined_in_current_crate(node_id: NodeId) -> bool { pub(super) fn parser_from_cx( psess: &ParseSess, - mut tts: TokenStream, + tts: ArenaTokenStream, recovery: Recovery, ) -> Parser<'_> { - tts.desugar_doc_comments(); - Parser::new(psess, ArenaTokenStream::from_stream(&tts), rustc_parse::MACRO_ARGUMENTS) - .recovery(recovery) + let tts = desugar_doc_comments(tts); + Parser::new(psess, tts, rustc_parse::MACRO_ARGUMENTS).recovery(recovery) +} + +/// Desugar doc comments like `/// foo` in the stream into `#[doc = +/// r"foo"]`. +fn desugar_doc_comments(stream: ArenaTokenStream) -> ArenaTokenStream { + // Fast path to avoid modifications + let mut doc_comment_found = false; + for tree in stream.iter_top_level_trees() { + if let ArenaTokenTree::Token(Token { kind: token::DocComment(..), .. }, ..) = tree { + doc_comment_found = true; + break; + } + } + if !doc_comment_found { + return stream; + } + + let mut builder = ArenaTokenStreamBuilder::with_capacity(stream.length()); + builder.build_from_stream(&stream, |builder, tree| { + if let ArenaTokenTree::Token( + Token { kind: token::DocComment(_, attr_style, data), span }, + _, + ) = tree + { + let span = *span; + // Searches for the occurrences of `"#*` and returns the minimum number of `#`s + // required to wrap the text. E.g. + // - `abc d` is wrapped as `r"abc d"` (num_of_hashes = 0) + // - `abc "d"` is wrapped as `r#"abc "d""#` (num_of_hashes = 1) + // - `abc "##d##"` is wrapped as `r###"abc ##"d"##"###` (num_of_hashes = 3) + let mut num_of_hashes = 0; + let mut count = 0; + for ch in data.as_str().chars() { + count = match ch { + '"' => 1, + '#' if count > 0 => count + 1, + _ => 0, + }; + num_of_hashes = cmp::max(num_of_hashes, count); + } + + if *attr_style == AttrStyle::Inner { + builder.push_token(Token::new(token::Pound, span), Spacing::Joint); + builder.push_token(Token::new(token::Bang, span), Spacing::JointHidden); + } else { + builder.push_token(Token::new(token::Pound, span), Spacing::JointHidden); + } + + // `/// foo` becomes `[doc = r"foo"]`. + let delim_span = DelimSpan::from_single(span); + let start = builder.start_delimited(); + builder + .push_token_alone(Token::new(token::Ident(sym::doc, token::IdentIsRaw::No), span)); + builder.push_token_alone(Token::new(token::Eq, span)); + builder.push_token_alone(Token::new( + TokenKind::lit(token::StrRaw(num_of_hashes), *data, None), + span, + )); + builder.close_delimited( + start, + DelimitedData { + span: delim_span, + delimiter: Delimiter::Bracket, + spacing: DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), + }, + ); + + PerTreeOp::Skip + } else { + PerTreeOp::Continue + } + }); + builder.finish() } diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 30c53b9f0604a..ab2ee1d0c44e8 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -40,21 +40,23 @@ impl base::BangProcMacro for BangProcMacro { &self, ecx: &mut ExtCtxt<'_>, span: Span, - input: TokenStream, + input: ArenaTokenStream, ) -> Result { let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); - self.client.run1(&strategy, server, input, proc_macro_backtrace).map_err(|e| { - ecx.dcx().emit_err(diagnostics::ProcMacroPanicked { - span, - message: e - .into_string() - .map(|message| diagnostics::ProcMacroPanickedHelp { message }), - }) - }) + self.client.run1(&strategy, server, input.to_token_stream(), proc_macro_backtrace).map_err( + |e| { + ecx.dcx().emit_err(diagnostics::ProcMacroPanicked { + span, + message: e + .into_string() + .map(|message| diagnostics::ProcMacroPanickedHelp { message }), + }) + }, + ) } } @@ -67,24 +69,30 @@ impl base::AttrProcMacro for AttrProcMacro { &self, ecx: &mut ExtCtxt<'_>, span: Span, - annotation: TokenStream, - annotated: TokenStream, + annotation: ArenaTokenStream, + annotated: ArenaTokenStream, ) -> Result { let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); - self.client.run2(&strategy, server, annotation, annotated, proc_macro_backtrace).map_err( - |e| { + self.client + .run2( + &strategy, + server, + annotation.to_token_stream(), + annotated.to_token_stream(), + proc_macro_backtrace, + ) + .map_err(|e| { ecx.dcx().emit_err(diagnostics::CustomAttributePanicked { span, message: e .into_string() .map(|message| diagnostics::CustomAttributePanickedHelp { message }), }) - }, - ) + }) } } @@ -114,7 +122,7 @@ impl MultiItemModifier for DeriveProcMacro { let res = if ecx.sess.opts.incremental.is_some() && ecx.sess.opts.unstable_opts.cache_proc_macros { - (*EXPAND_DERIVE_MACRO_CACHED)(invoc_id, input, ecx, self.client) + (*EXPAND_DERIVE_MACRO_CACHED)(invoc_id, input.to_token_stream(), ecx, self.client) } else { expand_derive_macro(invoc_id, input, ecx, self.client) }; @@ -165,7 +173,7 @@ type DeriveClient = pm::bridge::client::Client; pub fn expand_derive_macro( invoc_id: LocalExpnId, - input: TokenStream, + input: ArenaTokenStream, ecx: &mut ExtCtxt<'_>, client: DeriveClient, ) -> Result { @@ -181,7 +189,7 @@ pub fn expand_derive_macro( let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); - match client.run1(&strategy, server, input, proc_macro_backtrace) { + match client.run1(&strategy, server, input.to_token_stream(), proc_macro_backtrace) { Ok(stream) => Ok(stream), Err(e) => { let invoc_expn_data = invoc_id.expn_data(); diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 00b851c94314b..6731b5feb86fc 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -584,7 +584,7 @@ impl server::Server for Rustc<'_, '_> { } fn ts_to_string(&mut self, stream: &Self::TokenStream) -> String { - pprust::tts_to_string(stream) + pprust::tts_to_string(&ArenaTokenStream::from_stream(stream)) } fn ts_expand_expr(&mut self, stream: &Self::TokenStream) -> Result { diff --git a/compiler/rustc_expand_queries/src/derive.rs b/compiler/rustc_expand_queries/src/derive.rs index 1254013ad89bb..56dd73bfefe53 100644 --- a/compiler/rustc_expand_queries/src/derive.rs +++ b/compiler/rustc_expand_queries/src/derive.rs @@ -1,3 +1,4 @@ +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::tokenstream::TokenStream; use rustc_expand::base::ExtCtxt; use rustc_middle::ty::{TyCtxt, tls}; @@ -76,7 +77,12 @@ pub(crate) fn derive_macro_expansion<'tcx>( let _ = tcx.crate_hash(invoc_id.expn_data().macro_def_id.unwrap().krate); QueryDeriveExpandCtx::with(|ecx, client| { - rustc_expand::proc_macro::expand_derive_macro(invoc_id, input.clone(), ecx, client) - .map(|ts| &*tcx.arena.alloc(ts)) + rustc_expand::proc_macro::expand_derive_macro( + invoc_id, + ArenaTokenStream::from_stream(input), + ecx, + client, + ) + .map(|ts| &*tcx.arena.alloc(ts)) }) } From c58e81f0d9468bf50c9fbf4b705e40a9ac31e201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 15:15:03 +0200 Subject: [PATCH 22/25] Use `ArenaTokenStream` in more parts of macro expansion --- compiler/rustc_ast/src/tokenarena.rs | 4 + .../rustc_builtin_macros/src/contracts.rs | 79 +++++++++++-------- compiler/rustc_expand/src/base.rs | 16 ++-- compiler/rustc_expand/src/expand.rs | 10 +-- compiler/rustc_expand/src/mbe/macro_rules.rs | 10 +-- compiler/rustc_expand/src/proc_macro.rs | 20 +++-- 6 files changed, 80 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_ast/src/tokenarena.rs b/compiler/rustc_ast/src/tokenarena.rs index ee593a8c64bd5..f9ec9e39e6ddf 100644 --- a/compiler/rustc_ast/src/tokenarena.rs +++ b/compiler/rustc_ast/src/tokenarena.rs @@ -93,6 +93,10 @@ impl ArenaTokenStreamBuilder { Self { tokens: Vec::with_capacity(capacity), current_delimited_sequence: None } } + pub fn tokens(&self) -> &[ArenaTokenTree] { + &self.tokens + } + pub fn push_token(&mut self, token: Token, spacing: Spacing) { self.tokens.push(ArenaTokenTree::Token(token, spacing)); } diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index 65762e7b78e7d..e808ff9647baf 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -1,6 +1,9 @@ use rustc_ast::token; -use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::token::Delimiter; +use rustc_ast::tokenarena::{ + ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree, DelimitedData, PerTreeOp, +}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing}; use rustc_errors::ErrorGuaranteed; use rustc_expand::base::{AttrProcMacro, ExtCtxt}; use rustc_span::Span; @@ -17,7 +20,7 @@ impl AttrProcMacro for ExpandRequires { span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result { + ) -> Result { expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractRequires) } } @@ -29,7 +32,7 @@ impl AttrProcMacro for ExpandEnsures { span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result { + ) -> Result { expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractEnsures) } } @@ -48,20 +51,20 @@ impl AttrProcMacro for ExpandEnsures { fn expand_contract_clause( ecx: &mut ExtCtxt<'_>, attr_span: Span, - annotated: TokenStream, - inject: impl FnOnce(&mut Vec) -> Result<(), ErrorGuaranteed>, -) -> Result { - let mut new_tts = vec![]; - let mut cursor = annotated.iter(); + annotated: ArenaTokenStream, + inject: impl FnOnce(&mut ArenaTokenStreamBuilder) -> Result<(), ErrorGuaranteed>, +) -> Result { + let mut builder = ArenaTokenStreamBuilder::with_capacity(annotated.length()); + let mut cursor = annotated.iter_top_level_trees(); - let is_kw = |tt: &TokenTree, sym: Symbol| { - if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false } + let is_kw = |tt: &ArenaTokenTree, sym: Symbol| { + if let ArenaTokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false } }; // Find the `fn` keyword to check if this is a function. if cursor .find(|tt| { - new_tts.push((*tt).clone()); + builder.push_token_tree(&tt.to_token_tree(&annotated)); is_kw(tt, kw::Fn) }) .is_none() @@ -73,7 +76,7 @@ fn expand_contract_clause( } // Contracts are not yet supported on async/gen functions - if new_tts.iter().any(|tt| is_kw(tt, kw::Async) || is_kw(tt, kw::Gen)) { + if builder.tokens().iter().any(|tt| is_kw(tt, kw::Async) || is_kw(tt, kw::Gen)) { return Err(ecx.sess.dcx().span_err( attr_span, "contract annotations are not yet supported on async or gen functions", @@ -90,7 +93,11 @@ fn expand_contract_clause( }; // If `tt` is the last element. Check if it is the function body. if cursor.peek().is_none() { - if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt { + if let ArenaTokenTree::DelimitedStart( + _, + DelimitedData { delimiter: token::Delimiter::Brace, .. }, + ) = tt + { break tt; } else { return Err(ecx.sess.dcx().span_err( @@ -103,7 +110,7 @@ fn expand_contract_clause( if is_kw(tt, kw::Where) { break tt; } - new_tts.push(tt.clone()); + builder.push_token_tree(&tt.to_token_tree(&annotated)); }; // At this point, we've transcribed everything from the `fn` through the formal parameter list @@ -111,15 +118,21 @@ fn expand_contract_clause( // // Now inject the AST contract form. // - inject(&mut new_tts)?; + inject(&mut builder)?; // Above we injected the internal AST requires/ensures construct. Now copy over all the other // token trees. - new_tts.push(next_tt.clone()); + builder.push_token_tree(&next_tt.to_token_tree(&annotated)); while let Some(tt) = cursor.next() { - new_tts.push(tt.clone()); + builder.push_token_tree(&tt.to_token_tree(&annotated)); if cursor.peek().is_none() - && !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _)) + && !matches!( + tt, + ArenaTokenTree::DelimitedStart( + _, + DelimitedData { delimiter: token::Delimiter::Brace, .. } + ) + ) { return Err(ecx.sess.dcx().span_err( attr_span, @@ -128,7 +141,7 @@ fn expand_contract_clause( } } - Ok(TokenStream::new(new_tts)) + Ok(builder.finish()) } fn expand_contract_clause_tts( @@ -137,7 +150,7 @@ fn expand_contract_clause_tts( annotation: ArenaTokenStream, annotated: ArenaTokenStream, clause_keyword: rustc_span::Symbol, -) -> Result { +) -> Result { if annotation.is_empty() { let (name, example) = if clause_keyword == kw::ContractRequires { ("requires", "condition") @@ -150,21 +163,25 @@ fn expand_contract_clause_tts( ); // Returning `Err` would replace it with a dummy fragment and cause cascading name-resolution errors. // Instead, we return the original token stream so that there is no later noises. - return Ok(annotated.to_token_stream()); + return Ok(annotated); } let feature_span = ecx.with_def_site_ctxt(attr_span); - expand_contract_clause(ecx, attr_span, annotated.to_token_stream(), |new_tts| { - new_tts.push(TokenTree::Token( + expand_contract_clause(ecx, attr_span, annotated, |builder| { + builder.push_token( token::Token::from_ast_ident(Ident::new(clause_keyword, feature_span)), Spacing::Joint, - )); - new_tts.push(TokenTree::Delimited( - DelimSpan::from_single(attr_span), - DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), - token::Delimiter::Brace, - annotation.to_token_stream(), - )); + ); + let start = builder.start_delimited(); + builder.build_from_stream(&annotation, |_, _| PerTreeOp::Continue); + builder.close_delimited( + start, + DelimitedData { + span: DelimSpan::from_single(attr_span), + spacing: DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), + delimiter: Delimiter::Brace, + }, + ); Ok(()) }) } diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index c94b824021464..be54d43a7d39f 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -317,19 +317,19 @@ pub trait BangProcMacro { ecx: &'cx mut ExtCtxt<'_>, span: Span, ts: ArenaTokenStream, - ) -> Result; + ) -> Result; } impl BangProcMacro for F where - F: Fn(&mut ExtCtxt<'_>, Span, ArenaTokenStream) -> Result, + F: Fn(&mut ExtCtxt<'_>, Span, ArenaTokenStream) -> Result, { fn expand<'cx>( &self, ecx: &'cx mut ExtCtxt<'_>, span: Span, ts: ArenaTokenStream, - ) -> Result { + ) -> Result { // FIXME setup implicit context in TLS before calling self. self(ecx, span, ts) } @@ -342,7 +342,7 @@ pub trait AttrProcMacro { span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result; + ) -> Result; // Default implementation for safe attributes; override if the attribute can be unsafe. fn expand_with_safety<'cx>( @@ -352,7 +352,7 @@ pub trait AttrProcMacro { span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result { + ) -> Result { if let Safety::Unsafe(span) = safety { ecx.dcx().span_err(span, "unnecessary `unsafe` on safe attribute"); } @@ -362,7 +362,7 @@ pub trait AttrProcMacro { impl AttrProcMacro for F where - F: Fn(ArenaTokenStream, ArenaTokenStream) -> TokenStream, + F: Fn(ArenaTokenStream, ArenaTokenStream) -> ArenaTokenStream, { fn expand<'cx>( &self, @@ -370,7 +370,7 @@ where _span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result { + ) -> Result { // FIXME setup implicit context in TLS before calling self. Ok(self(annotation, annotated)) } @@ -936,7 +936,7 @@ impl SyntaxExtension { ecx: &mut ExtCtxt<'_>, span: Span, _ts: ArenaTokenStream, - ) -> Result { + ) -> Result { Err(ecx.dcx().span_delayed_bug(span, "expanded a dummy bang macro")) } SyntaxExtension::default(SyntaxExtensionKind::Bang(Arc::new(expand)), edition) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index d5513030ebec5..e8e728fcd47d8 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -727,12 +727,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if let SyntaxExtensionKind::Bang(expander) = ext { match expander.expand(self.cx, span, mac.args.tokens.clone()) { Ok(tok_result) => { - let fragment = self.parse_ast_fragment( - ArenaTokenStream::from_stream(&tok_result), - fragment_kind, - &mac.path, - span, - ); + let fragment = + self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span); if macro_stats { update_bang_macro_stats( self.cx, @@ -840,7 +836,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( - ArenaTokenStream::from_stream(&tok_result), + tok_result, fragment_kind, &attr_item.path, span, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index ef4b701862cf8..5d19cafbdfe0d 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -322,7 +322,7 @@ impl AttrProcMacro for MacroRulesMacroExpander { _sp: Span, _args: ArenaTokenStream, _body: ArenaTokenStream, - ) -> Result { + ) -> Result { unreachable!("`expand` called on `MacroRulesMacroExpander`, expected `expand_with_safety`") } @@ -333,7 +333,7 @@ impl AttrProcMacro for MacroRulesMacroExpander { sp: Span, args: ArenaTokenStream, body: ArenaTokenStream, - ) -> Result { + ) -> Result { expand_macro_attr( cx, sp, @@ -358,7 +358,7 @@ impl BangProcMacro for DummyBang { _: &'cx mut ExtCtxt<'_>, _: Span, _: ArenaTokenStream, - ) -> Result { + ) -> Result { Err(self.0) } } @@ -526,7 +526,7 @@ fn expand_macro_attr( body: ArenaTokenStream, rules: &[MacroRule], on_unmatched_args: Option<&Directive>, -) -> Result { +) -> Result { let psess = &cx.sess.psess; // Macros defined in the current crate have a real node id, // whereas macros from an external crate have a dummy id. @@ -582,7 +582,7 @@ fn expand_macro_attr( cx.resolver.record_macro_rule_usage(node_id, i); } - Ok(tts.to_token_stream()) + Ok(tts) } Err(CanRetry::No(guar)) => Err(guar), Err(CanRetry::Yes) => { diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index ab2ee1d0c44e8..1deab13abcc6e 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -41,22 +41,24 @@ impl base::BangProcMacro for BangProcMacro { ecx: &mut ExtCtxt<'_>, span: Span, input: ArenaTokenStream, - ) -> Result { + ) -> Result { let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); - self.client.run1(&strategy, server, input.to_token_stream(), proc_macro_backtrace).map_err( - |e| { + let stream = self + .client + .run1(&strategy, server, input.to_token_stream(), proc_macro_backtrace) + .map_err(|e| { ecx.dcx().emit_err(diagnostics::ProcMacroPanicked { span, message: e .into_string() .map(|message| diagnostics::ProcMacroPanickedHelp { message }), }) - }, - ) + }); + stream.map(|stream| ArenaTokenStream::from_stream(&stream)) } } @@ -71,13 +73,14 @@ impl base::AttrProcMacro for AttrProcMacro { span: Span, annotation: ArenaTokenStream, annotated: ArenaTokenStream, - ) -> Result { + ) -> Result { let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); - self.client + let stream = self + .client .run2( &strategy, server, @@ -92,7 +95,8 @@ impl base::AttrProcMacro for AttrProcMacro { .into_string() .map(|message| diagnostics::CustomAttributePanickedHelp { message }), }) - }) + }); + stream.map(|stream| ArenaTokenStream::from_stream(&stream)) } } From 704b01366c3e000ba8a21281105249255425e598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 15:41:35 +0200 Subject: [PATCH 23/25] Use `ArenaTokenStream` in derive proc macro expansion --- compiler/rustc_expand/src/proc_macro.rs | 20 +++++++++---------- compiler/rustc_expand_queries/src/derive.rs | 20 +++++++------------ compiler/rustc_middle/src/arena.rs | 2 ++ compiler/rustc_middle/src/queries.rs | 4 ++-- compiler/rustc_middle/src/query/erase.rs | 4 ++-- compiler/rustc_middle/src/query/keys.rs | 4 ++-- .../rustc_middle/src/query/on_disk_cache.rs | 2 +- 7 files changed, 26 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 1deab13abcc6e..c75559785297b 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,6 +1,5 @@ use rustc_ast as ast; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_data_structures::AtomicRef; use rustc_data_structures::profiling::TimingGuard; use rustc_errors::ErrorGuaranteed; @@ -126,7 +125,7 @@ impl MultiItemModifier for DeriveProcMacro { let res = if ecx.sess.opts.incremental.is_some() && ecx.sess.opts.unstable_opts.cache_proc_macros { - (*EXPAND_DERIVE_MACRO_CACHED)(invoc_id, input.to_token_stream(), ecx, self.client) + (*EXPAND_DERIVE_MACRO_CACHED)(invoc_id, input, ecx, self.client) } else { expand_derive_macro(invoc_id, input, ecx, self.client) }; @@ -137,11 +136,7 @@ impl MultiItemModifier for DeriveProcMacro { }; let error_count_before = ecx.dcx().err_count(); - let mut parser = Parser::new( - &ecx.sess.psess, - ArenaTokenStream::from_stream(&output), - Some("proc-macro derive"), - ); + let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive")); let mut items = vec![]; loop { @@ -180,7 +175,7 @@ pub fn expand_derive_macro( input: ArenaTokenStream, ecx: &mut ExtCtxt<'_>, client: DeriveClient, -) -> Result { +) -> Result { let _timer = ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| { let invoc_expn_data = invoc_id.expn_data(); @@ -194,7 +189,7 @@ pub fn expand_derive_macro( let server = proc_macro_server::Rustc::new(ecx); match client.run1(&strategy, server, input.to_token_stream(), proc_macro_backtrace) { - Ok(stream) => Ok(stream), + Ok(stream) => Ok(ArenaTokenStream::from_stream(&stream)), Err(e) => { let invoc_expn_data = invoc_id.expn_data(); let span = invoc_expn_data.call_site; @@ -212,7 +207,12 @@ pub fn expand_derive_macro( } pub static EXPAND_DERIVE_MACRO_CACHED: AtomicRef< - fn(LocalExpnId, TokenStream, &mut ExtCtxt<'_>, DeriveClient) -> Result, + fn( + LocalExpnId, + ArenaTokenStream, + &mut ExtCtxt<'_>, + DeriveClient, + ) -> Result, > = AtomicRef::new( &(|_, _, _: &mut ExtCtxt<'_>, _| -> Result<_, _> { panic!( diff --git a/compiler/rustc_expand_queries/src/derive.rs b/compiler/rustc_expand_queries/src/derive.rs index 56dd73bfefe53..921ae7265853c 100644 --- a/compiler/rustc_expand_queries/src/derive.rs +++ b/compiler/rustc_expand_queries/src/derive.rs @@ -1,5 +1,4 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_expand::base::ExtCtxt; use rustc_middle::ty::{TyCtxt, tls}; use rustc_proc_macro as pm; @@ -54,13 +53,13 @@ scoped_tls::scoped_thread_local!(static DERIVE_EXPAND_CTX: QueryDeriveExpandCtx) pub(crate) fn expand_derive_macro_cached( invoc_id: LocalExpnId, - input: TokenStream, + input: ArenaTokenStream, ecx: &mut ExtCtxt<'_>, client: DeriveClient, -) -> Result { +) -> Result { tls::with(|tcx| { let input = &*tcx.arena.alloc(input); - let key: (LocalExpnId, &TokenStream) = (invoc_id, input); + let key: (LocalExpnId, &ArenaTokenStream) = (invoc_id, input); QueryDeriveExpandCtx::enter(ecx, client, move || tcx.derive_macro_expansion(key).cloned()) }) @@ -69,20 +68,15 @@ pub(crate) fn expand_derive_macro_cached( /// Provide a query for computing the output of a derive macro. pub(crate) fn derive_macro_expansion<'tcx>( tcx: TyCtxt<'tcx>, - key: (LocalExpnId, &'tcx TokenStream), -) -> Result<&'tcx TokenStream, ()> { + key: (LocalExpnId, &'tcx ArenaTokenStream), +) -> Result<&'tcx ArenaTokenStream, ()> { let (invoc_id, input) = key; // Make sure that we invalidate the query when the crate defining the proc macro changes let _ = tcx.crate_hash(invoc_id.expn_data().macro_def_id.unwrap().krate); QueryDeriveExpandCtx::with(|ecx, client| { - rustc_expand::proc_macro::expand_derive_macro( - invoc_id, - ArenaTokenStream::from_stream(input), - ecx, - client, - ) - .map(|ts| &*tcx.arena.alloc(ts)) + rustc_expand::proc_macro::expand_derive_macro(invoc_id, input.clone(), ecx, client) + .map(|ts| &*tcx.arena.alloc(ts)) }) } diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index bfaef6157d02c..b62a85d02ba83 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -136,6 +136,7 @@ rustc_arena::declare_arena! { crate_inherent_impls: rustc_middle::ty::CrateInherentImpls, hir_owner_nodes: rustc_hir::OwnerNodes<'tcx>, token_stream: rustc_ast::tokenstream::TokenStream, + arena_token_stream: rustc_ast::tokenarena::ArenaTokenStream, parenting: rustc_hir::def_id::LocalDefIdMap, trait_candidates: rustc_hir::ItemLocalMap<&'tcx [rustc_hir::TraitCandidate<'tcx>]>, delayed_lints: rustc_data_structures::steal::Steal, @@ -195,6 +196,7 @@ impl_ref_decodable_into_arena! { (rustc_middle::middle::exported_symbols::ExportedSymbol<'tcx>, rustc_middle::middle::exported_symbols::SymbolExportInfo), rustc_ast::InlineAsmTemplatePiece, rustc_ast::tokenstream::TokenStream, + rustc_ast::tokenarena::ArenaTokenStream, rustc_data_structures::unord::UnordMap>>, rustc_data_structures::unord::UnordSet, rustc_hir::Attribute, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index aa354e1ab8df0..15d22b3bc8058 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -52,7 +52,7 @@ use rustc_abi::Align; use rustc_arena::TypedArena; use rustc_ast as ast; use rustc_ast::expand::allocator::AllocatorKind; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_attr_ir::lang_items::{LangItem, LanguageItems}; use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_crate_store::{ @@ -148,7 +148,7 @@ rustc_queries! { /// - Token stream which serves as an input to the macro. /// /// The output is the token stream generated by the proc macro. - query derive_macro_expansion(key: (LocalExpnId, &'tcx TokenStream)) -> Result<&'tcx TokenStream, ()> { + query derive_macro_expansion(key: (LocalExpnId, &'tcx ArenaTokenStream)) -> Result<&'tcx ArenaTokenStream, ()> { desc { "expanding a derive (proc) macro" } cache_on_disk } diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 15a684491dcab..cf20fc3ef8b4c 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -10,7 +10,7 @@ use std::intrinsics::transmute_unchecked; use std::marker::PhantomData; use std::mem::MaybeUninit; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_index::{Idx, IndexSlice}; @@ -204,7 +204,7 @@ impl_erasable_for_types_with_no_type_params! { Option>>, Option>, Option, - Result<&'_ TokenStream, ()>, + Result<&'_ ArenaTokenStream, ()>, Result<&'_ rustc_target::callconv::FnAbi<'_, Ty<'_>>, &'_ ty::layout::FnAbiError<'_>>, Result<&'_ traits::ImplSource<'_, ()>, traits::CodegenObligationError>, Result<&'_ ty::List>, ty::util::AlwaysRequiresDrop>, diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index eae4148a30ed4..cec91b09b2007 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -4,7 +4,7 @@ use std::ffi::OsStr; use std::fmt::Debug; use std::hash::Hash; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_data_structures::sso::SsoHashSet; use rustc_data_structures::stable_hash::StableHash; use rustc_hir::OwnerId; @@ -394,7 +394,7 @@ impl<'tcx> QueryKey for ty::Value<'tcx> { } } -impl<'tcx> QueryKey for (LocalExpnId, &'tcx TokenStream) { +impl<'tcx> QueryKey for (LocalExpnId, &'tcx ArenaTokenStream) { fn default_span(&self, _tcx: TyCtxt<'_>) -> Span { self.0.expn_data().call_site } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index d743c5dcc7e43..52487006a6b75 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -742,7 +742,7 @@ impl<'a, 'tcx> Decodable> } } -impl<'a, 'tcx> Decodable> for &'tcx rustc_ast::tokenstream::TokenStream { +impl<'a, 'tcx> Decodable> for &'tcx rustc_ast::tokenarena::ArenaTokenStream { #[inline] fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { RefDecodable::decode(d) From 4bee40786c66bad2286079cd76604f63da1f4a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 15:43:19 +0200 Subject: [PATCH 24/25] Use `ArenaTokenStream` in `edition_2024_expr_fragment_specifier` --- ...acro_expr_fragment_specifier_2024_migration.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs index 9419eee5df061..d20f67cc0f254 100644 --- a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs +++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs @@ -1,7 +1,7 @@ //! Migration code for the `expr_fragment_specifier_2024` rule. use rustc_ast::token::{Token, TokenKind}; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenTree, ArenaTokenTreeIter}; use rustc_lint_defs::{declare_lint, declare_lint_pass, fcw}; use rustc_span::edition::Edition; use rustc_span::sym; @@ -78,17 +78,18 @@ declare_lint! { declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]); impl Expr2024 { - fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) { + fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: ArenaTokenTreeIter) { let mut prev_colon = false; let mut prev_identifier = false; let mut prev_dollar = false; - for tt in tokens.iter() { + let stream = tokens.stream().clone(); + for tt in tokens { debug!( "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}", tt ); match tt { - TokenTree::Token(token, _) => match token.kind { + ArenaTokenTree::Token(token, _) => match token.kind { TokenKind::Dollar => { prev_dollar = true; continue; @@ -109,7 +110,9 @@ impl Expr2024 { } _ => {} }, - TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), + ArenaTokenTree::DelimitedStart(bounds, _) => { + self.check_tokens(cx, stream.iter_delimited(bounds)) + } } prev_colon = false; prev_identifier = false; @@ -142,6 +145,6 @@ impl Expr2024 { impl EarlyLintPass for Expr2024 { fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) { - self.check_tokens(cx, &mc.body.tokens.to_token_stream()); + self.check_tokens(cx, mc.body.tokens.iter_top_level_trees()); } } From 167dbb4b934fe874a10203a3f26041bf75e99b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 12 Sep 2026 15:50:44 +0200 Subject: [PATCH 25/25] Use `ArenaTokenStream` in builtin macros --- compiler/rustc_builtin_macros/src/asm.rs | 15 +++---- compiler/rustc_builtin_macros/src/assert.rs | 14 +++--- compiler/rustc_builtin_macros/src/cfg.rs | 11 +++-- .../rustc_builtin_macros/src/cfg_select.rs | 4 +- .../rustc_builtin_macros/src/compile_error.rs | 4 +- compiler/rustc_builtin_macros/src/concat.rs | 4 +- .../rustc_builtin_macros/src/concat_bytes.rs | 4 +- .../src/direct_const_arg.rs | 4 +- .../rustc_builtin_macros/src/edition_panic.rs | 10 ++--- compiler/rustc_builtin_macros/src/env.rs | 6 +-- compiler/rustc_builtin_macros/src/format.rs | 11 +++-- compiler/rustc_builtin_macros/src/iter.rs | 7 ++- .../rustc_builtin_macros/src/log_syntax.rs | 5 +-- .../rustc_builtin_macros/src/pattern_type.rs | 7 ++- .../rustc_builtin_macros/src/source_util.rs | 27 ++++++------ .../src/test_binder_constraints.rs | 5 +-- .../rustc_builtin_macros/src/trace_macros.rs | 10 ++--- compiler/rustc_builtin_macros/src/util.rs | 15 +++---- .../rustc_builtin_macros/src/view_type.rs | 7 ++- compiler/rustc_expand/src/base.rs | 6 +-- ..._expr_fragment_specifier_2024_migration.rs | 2 +- compiler/rustc_parse/src/parser/mod.rs | 43 ++++++++++--------- 22 files changed, 107 insertions(+), 114 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index b98f7c28e91c5..233148f96c672 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -1,6 +1,5 @@ use rustc_ast as ast; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::PResult; @@ -30,14 +29,10 @@ struct ValidatedAsmArgs { fn parse_args<'a>( ecx: &ExtCtxt<'a>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, asm_macro: AsmMacro, ) -> PResult<'a, ValidatedAsmArgs> { - let args = parse_asm_args( - &mut ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)), - sp, - asm_macro, - )?; + let args = parse_asm_args(&mut ecx.new_parser_from_tts(tts), sp, asm_macro)?; validate_asm_args(ecx, asm_macro, args) } @@ -587,7 +582,7 @@ fn expand_preparsed_asm( pub(super) fn expand_asm<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::Asm) { Ok(args) => { @@ -616,7 +611,7 @@ pub(super) fn expand_asm<'cx>( pub(super) fn expand_naked_asm<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::NakedAsm) { Ok(args) => { @@ -646,7 +641,7 @@ pub(super) fn expand_naked_asm<'cx>( pub(super) fn expand_global_asm<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::GlobalAsm) { Ok(args) => { diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index af47f347991e5..dfc5fe7e34a9d 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -2,7 +2,7 @@ mod context; use rustc_ast::token::Delimiter; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token}; use rustc_ast_pretty::pprust; use rustc_errors::PResult; @@ -18,7 +18,7 @@ use crate::edition_panic::use_panic_2021; pub(crate) fn expand_assert<'cx>( cx: &'cx mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) { Ok(assert) => assert, @@ -59,7 +59,7 @@ pub(crate) fn expand_assert<'cx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(call_site_span), delim: Delimiter::Parenthesis, - tokens: ArenaTokenStream::from_stream(&tokens), + tokens, }), })), ); @@ -96,7 +96,7 @@ pub(crate) fn expand_assert<'cx>( struct Assert { cond_expr: Box, - custom_message: Option, + custom_message: Option, } // if !{ ... } { ... } else { ... } @@ -110,8 +110,8 @@ fn expr_if_not( cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els) } -fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { - let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); +fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: ArenaTokenStream) -> PResult<'a, Assert> { + let mut parser = cx.new_parser_from_tts(stream); if parser.token == token::Eof { return Err(cx.dcx().create_err(diagnostics::AssertRequiresBoolean { span: sp })); @@ -157,7 +157,7 @@ fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult< Ok(Assert { cond_expr, custom_message }) } -fn parse_custom_message(parser: &mut Parser<'_>) -> Option { +fn parse_custom_message(parser: &mut Parser<'_>) -> Option { let ts = parser.parse_tokens(); if !ts.is_empty() { Some(ts) } else { None } } diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index f4518de8706bb..c595e072940af 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -3,7 +3,6 @@ //! current compilation environment. use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrStyle, token}; use rustc_attr_ir::target::Target; use rustc_attr_ir::{AttrPath, CfgEntry}; @@ -22,7 +21,7 @@ use crate::diagnostics; pub(crate) fn expand_cfg( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); @@ -36,8 +35,12 @@ pub(crate) fn expand_cfg( }) } -fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result { - let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); +fn parse_cfg( + cx: &ExtCtxt<'_>, + span: Span, + tts: ArenaTokenStream, +) -> Result { + let mut parser = cx.new_parser_from_tts(tts); if parser.token == token::Eof { return Err(cx.dcx().emit_err(diagnostics::RequiresCfgPattern { span })); } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index fc9edd7d1fe41..12349468966c9 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -119,11 +119,11 @@ impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> { pub(super) fn expand_cfg_select<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { ExpandResult::Ready( match parse_cfg_select( - &mut ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)), + &mut ecx.new_parser_from_tts(tts), ecx.sess, Some(ecx.ecfg.features), ecx.current_expansion.lint_node_id, diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs index e2109caf2e59d..ad3d3cdcc2d05 100644 --- a/compiler/rustc_builtin_macros/src/compile_error.rs +++ b/compiler/rustc_builtin_macros/src/compile_error.rs @@ -1,6 +1,6 @@ // The compiler code necessary to support the compile_error! extension. -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; use rustc_span::Span; @@ -9,7 +9,7 @@ use crate::util::get_single_str_from_tts; pub(crate) fn expand_compile_error<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "compile_error!") else { return ExpandResult::Retry(()); diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index a260b3c43e8af..bab55a3653f5b 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -1,4 +1,4 @@ -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::{ExprKind, LitKind, UnOp}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_session::diagnostics::report_lit_error; @@ -10,7 +10,7 @@ use crate::util::get_exprs_from_tts; pub(crate) fn expand_concat( cx: &mut ExtCtxt<'_>, sp: rustc_span::Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { return ExpandResult::Retry(()); diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index 15d0f43d039f2..fbf3d71dd3e98 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -1,4 +1,4 @@ -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::{ExprKind, LitIntType, LitKind, StrStyle, UintTy, token}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_session::diagnostics::report_lit_error; @@ -135,7 +135,7 @@ fn handle_array_element( pub(crate) fn expand_concat_bytes( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { return ExpandResult::Retry(()); diff --git a/compiler/rustc_builtin_macros/src/direct_const_arg.rs b/compiler/rustc_builtin_macros/src/direct_const_arg.rs index 6af503e9a4681..f5856ae2380f2 100644 --- a/compiler/rustc_builtin_macros/src/direct_const_arg.rs +++ b/compiler/rustc_builtin_macros/src/direct_const_arg.rs @@ -1,5 +1,5 @@ use rustc_ast::ast; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; use rustc_span::Span; @@ -8,7 +8,7 @@ use crate::util::get_single_expr_from_tts; pub(crate) fn expand<'cx>( cx: &'cx mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let ExpandResult::Ready(expr) = get_single_expr_from_tts(cx, span, tts, "direct_const_arg!") else { diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs index 3fa49a3921d25..d7adb9aa07e83 100644 --- a/compiler/rustc_builtin_macros/src/edition_panic.rs +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -1,6 +1,6 @@ use rustc_ast::token::Delimiter; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::tokenstream::DelimSpan; use rustc_ast::*; use rustc_expand::base::*; use rustc_span::edition::Edition; @@ -18,7 +18,7 @@ use rustc_span::{Span, sym}; pub(crate) fn expand_panic<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let mac = if use_panic_2021(sp) { sym::panic_2021 } else { sym::panic_2015 }; expand(mac, cx, sp, tts) @@ -31,7 +31,7 @@ pub(crate) fn expand_panic<'cx>( pub(crate) fn expand_unreachable<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let mac = if use_panic_2021(sp) { sym::unreachable_2021 } else { sym::unreachable_2015 }; expand(mac, cx, sp, tts) @@ -41,7 +41,7 @@ fn expand<'cx>( mac: rustc_span::Symbol, cx: &'cx ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let sp = cx.with_call_site_ctxt(sp); @@ -60,7 +60,7 @@ fn expand<'cx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(sp), delim: Delimiter::Parenthesis, - tokens: ArenaTokenStream::from_stream(&tts), + tokens: tts, }), })), ), diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index cced54e3cb534..36b88ed71dc2a 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -6,7 +6,7 @@ use std::env; use std::env::VarError; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenarena::ArenaTokenStream; use rustc_ast::{GenericArg, Mutability}; use rustc_ast_pretty::pprust; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; @@ -26,7 +26,7 @@ fn lookup_env(var: Symbol) -> Result { pub(crate) fn expand_option_env<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let ExpandResult::Ready(mac_expr) = get_single_expr_from_tts(cx, sp, tts, "option_env!") else { return ExpandResult::Retry(()); @@ -80,7 +80,7 @@ pub(crate) fn expand_option_env<'cx>( pub(crate) fn expand_env<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { return ExpandResult::Retry(()); diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 7a17c5da5e42f..fc9fd36f283a3 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,7 +1,6 @@ use std::ops::Range; use parse::Position::ArgumentNamed; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, @@ -69,8 +68,8 @@ struct MacroInput { /// ```text /// Ok((fmtstr, parsed arguments)) /// ``` -fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { - let mut p = ecx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); +fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: ArenaTokenStream) -> PResult<'a, MacroInput> { + let mut p = ecx.new_parser_from_tts(tts); // parse the format string let fmtstr = match p.token.kind { @@ -1128,7 +1127,7 @@ fn report_invalid_references( fn expand_format_args_impl<'cx>( ecx: &'cx mut ExtCtxt<'_>, mut sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, nl: bool, ) -> MacroExpanderResult<'cx> { sp = ecx.with_def_site_ctxt(sp); @@ -1154,7 +1153,7 @@ fn expand_format_args_impl<'cx>( pub(crate) fn expand_format_args<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { expand_format_args_impl(ecx, sp, tts, false) } @@ -1162,7 +1161,7 @@ pub(crate) fn expand_format_args<'cx>( pub(crate) fn expand_format_args_nl<'cx>( ecx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { expand_format_args_impl(ecx, sp, tts, true) } diff --git a/compiler/rustc_builtin_macros/src/iter.rs b/compiler/rustc_builtin_macros/src/iter.rs index 0e781220b6b91..067e0f5ed80f6 100644 --- a/compiler/rustc_builtin_macros/src/iter.rs +++ b/compiler/rustc_builtin_macros/src/iter.rs @@ -1,5 +1,4 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{CoroutineKind, CoroutineMarker, Expr, ast, token}; use rustc_errors::PResult; use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; @@ -8,7 +7,7 @@ use rustc_span::Span; pub(crate) fn expand<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let closure = match parse_closure(cx, sp, tts) { Ok(parsed) => parsed, @@ -23,9 +22,9 @@ pub(crate) fn expand<'cx>( fn parse_closure<'a>( cx: &mut ExtCtxt<'a>, span: Span, - stream: TokenStream, + stream: ArenaTokenStream, ) -> PResult<'a, Box> { - let mut closure_parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); + let mut closure_parser = cx.new_parser_from_tts(stream); let coroutine_marker = Some(CoroutineMarker::new(CoroutineKind::Gen, span)); diff --git a/compiler/rustc_builtin_macros/src/log_syntax.rs b/compiler/rustc_builtin_macros/src/log_syntax.rs index c003dbab17e0f..b6ce04ae320b1 100644 --- a/compiler/rustc_builtin_macros/src/log_syntax.rs +++ b/compiler/rustc_builtin_macros/src/log_syntax.rs @@ -1,14 +1,13 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast_pretty::pprust; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; pub(crate) fn expand_log_syntax<'cx>( _cx: &'cx mut ExtCtxt<'_>, sp: rustc_span::Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { - println!("{}", pprust::tts_to_string(&ArenaTokenStream::from_stream(&tts))); + println!("{}", pprust::tts_to_string(&tts)); // any so that `log_syntax` can be invoked as an expression and item. ExpandResult::Ready(DummyResult::any_valid(sp)) diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs index 7fcc4a002925e..7f9302d9235b2 100644 --- a/compiler/rustc_builtin_macros/src/pattern_type.rs +++ b/compiler/rustc_builtin_macros/src/pattern_type.rs @@ -1,5 +1,4 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AnonConst, DUMMY_NODE_ID, Ty, TyPat, TyPatKind, ast, token}; use rustc_errors::PResult; use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; @@ -10,7 +9,7 @@ use rustc_span::Span; pub(crate) fn expand<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let (ty, pat) = match parse_pat_ty(cx, tts) { Ok(parsed) => parsed, @@ -24,9 +23,9 @@ pub(crate) fn expand<'cx>( fn parse_pat_ty<'a>( cx: &mut ExtCtxt<'a>, - stream: TokenStream, + stream: ArenaTokenStream, ) -> PResult<'a, (Box, Box)> { - let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); + let mut parser = cx.new_parser_from_tts(stream); let ty = parser.parse_ty()?; parser.expect_keyword(exp!(Is))?; diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index fd2da4260904f..8ca26def50541 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use rustc_ast as ast; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{join_path_idents, token}; use rustc_ast_pretty::pprust; use rustc_expand::base::{ @@ -31,10 +30,10 @@ use crate::util::{ pub(crate) fn expand_line( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - check_zero_tts(cx, sp, tts, "line!"); + check_zero_tts(cx, sp, &tts, "line!"); let topmost = cx.expansion_cause().unwrap_or(sp); let loc = cx.source_map().lookup_char_pos(topmost.lo()); @@ -46,10 +45,10 @@ pub(crate) fn expand_line( pub(crate) fn expand_column( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - check_zero_tts(cx, sp, tts, "column!"); + check_zero_tts(cx, sp, &tts, "column!"); let topmost = cx.expansion_cause().unwrap_or(sp); let loc = cx.source_map().lookup_char_pos(topmost.lo()); @@ -61,10 +60,10 @@ pub(crate) fn expand_column( pub(crate) fn expand_file( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - check_zero_tts(cx, sp, tts, "file!"); + check_zero_tts(cx, sp, &tts, "file!"); let topmost = cx.expansion_cause().unwrap_or(sp); let loc = cx.source_map().lookup_char_pos(topmost.lo()); @@ -80,10 +79,10 @@ pub(crate) fn expand_file( pub(crate) fn expand_stringify( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - let s = pprust::tts_to_string(&ArenaTokenStream::from_stream(&tts)); + let s = pprust::tts_to_string(&tts); ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s)))) } @@ -91,10 +90,10 @@ pub(crate) fn expand_stringify( pub(crate) fn expand_mod( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); - check_zero_tts(cx, sp, tts, "module_path!"); + check_zero_tts(cx, sp, &tts, "module_path!"); let mod_path = &cx.current_expansion.module.mod_path; let string = join_path_idents(mod_path); @@ -107,7 +106,7 @@ pub(crate) fn expand_mod( pub(crate) fn expand_include<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let sp = cx.with_def_site_ctxt(sp); let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include!") else { @@ -205,7 +204,7 @@ pub(crate) fn expand_include<'cx>( pub(crate) fn expand_include_str( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!") @@ -239,7 +238,7 @@ pub(crate) fn expand_include_str( pub(crate) fn expand_include_bytes( cx: &mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'static> { let sp = cx.with_def_site_ctxt(sp); let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!") diff --git a/compiler/rustc_builtin_macros/src/test_binder_constraints.rs b/compiler/rustc_builtin_macros/src/test_binder_constraints.rs index 792300f01b60f..e46b13caffe15 100644 --- a/compiler/rustc_builtin_macros/src/test_binder_constraints.rs +++ b/compiler/rustc_builtin_macros/src/test_binder_constraints.rs @@ -1,5 +1,4 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrVec, VisibilityKind, ast, token}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_span::Span; @@ -10,10 +9,10 @@ use crate::diagnostics; pub(crate) fn expand<'cx>( cx: &'cx mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let name = "test_binder_constraints!"; - let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); + let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { cx.dcx().emit_err(diagnostics::OnlyOneArgument { span, name }); }; diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs index 88837e01a9407..c99516742b28e 100644 --- a/compiler/rustc_builtin_macros/src/trace_macros.rs +++ b/compiler/rustc_builtin_macros/src/trace_macros.rs @@ -1,4 +1,4 @@ -use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; use rustc_span::{Span, kw}; @@ -7,13 +7,13 @@ use crate::diagnostics; pub(crate) fn expand_trace_macros( cx: &mut ExtCtxt<'_>, sp: Span, - tt: TokenStream, + tt: ArenaTokenStream, ) -> MacroExpanderResult<'static> { - let mut iter = tt.iter(); + let mut iter = tt.iter_top_level_trees(); let mut err = false; let value = match iter.next() { - Some(TokenTree::Token(token, _)) if token.is_keyword(kw::True) => true, - Some(TokenTree::Token(token, _)) if token.is_keyword(kw::False) => false, + Some(ArenaTokenTree::Token(token, _)) if token.is_keyword(kw::True) => true, + Some(ArenaTokenTree::Token(token, _)) if token.is_keyword(kw::False) => false, _ => { err = true; false diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index c8de0df2dce97..5d6a183245b5e 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,5 +1,4 @@ use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; use rustc_attr_parsing::{AttributeTemplate, validate_attr}; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; @@ -148,7 +147,7 @@ pub(crate) fn expr_to_string( /// returns even when `tts` is non-empty, macros that *need* to stop /// compilation should call `cx.diagnostic().abort_if_errors()` /// (this should be done as rarely as possible). -pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) { +pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: &ArenaTokenStream, name: &str) { if !tts.is_empty() { cx.dcx().emit_err(diagnostics::TakesNoArguments { span, name }); } @@ -171,7 +170,7 @@ pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result, E pub(crate) fn get_single_str_from_tts( cx: &mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, name: &str, ) -> ExpandResult, ()> { get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s)) @@ -180,7 +179,7 @@ pub(crate) fn get_single_str_from_tts( pub(crate) fn get_single_str_spanned_from_tts( cx: &mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, name: &str, ) -> ExpandResult, ()> { let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else { @@ -204,10 +203,10 @@ pub(crate) fn get_single_str_spanned_from_tts( pub(crate) fn get_single_expr_from_tts( cx: &mut ExtCtxt<'_>, span: Span, - tts: TokenStream, + tts: ArenaTokenStream, name: &str, ) -> ExpandResult, ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); + let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { let guar = cx.dcx().emit_err(diagnostics::OnlyOneArgument { span, name }); return ExpandResult::Ready(Err(guar)); @@ -228,9 +227,9 @@ pub(crate) fn get_single_expr_from_tts( /// On error, emit it, and return `Err`. pub(crate) fn get_exprs_from_tts( cx: &mut ExtCtxt<'_>, - tts: TokenStream, + tts: ArenaTokenStream, ) -> ExpandResult>, ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&tts)); + let mut p = cx.new_parser_from_tts(tts); let mut es = Vec::new(); while p.token != token::Eof { let expr = match parse_expr(&mut p) { diff --git a/compiler/rustc_builtin_macros/src/view_type.rs b/compiler/rustc_builtin_macros/src/view_type.rs index bfbac41715553..6818aca1a65ac 100644 --- a/compiler/rustc_builtin_macros/src/view_type.rs +++ b/compiler/rustc_builtin_macros/src/view_type.rs @@ -1,6 +1,5 @@ use rustc_ast::token::TokenKind; use rustc_ast::tokenarena::ArenaTokenStream; -use rustc_ast::tokenstream::TokenStream; use rustc_ast::{Ty, ast}; use rustc_errors::PResult; use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; @@ -11,7 +10,7 @@ use thin_vec::ThinVec; pub(crate) fn expand<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, - tts: TokenStream, + tts: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { let (ty, pat) = match parse_view_ty(cx, tts) { Ok(parsed) => parsed, @@ -25,9 +24,9 @@ pub(crate) fn expand<'cx>( fn parse_view_ty<'a>( cx: &mut ExtCtxt<'a>, - stream: TokenStream, + stream: ArenaTokenStream, ) -> PResult<'a, (Box, ThinVec)> { - let mut parser = cx.new_parser_from_tts(ArenaTokenStream::from_stream(&stream)); + let mut parser = cx.new_parser_from_tts(stream); let ty = parser.parse_ty()?; diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index be54d43a7d39f..6604379524aaa 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -389,11 +389,11 @@ pub trait TTMacroExpander: Any { pub type MacroExpanderResult<'cx> = ExpandResult, ()>; pub type MacroExpanderFn = - for<'cx> fn(&'cx mut ExtCtxt<'_>, Span, TokenStream) -> MacroExpanderResult<'cx>; + for<'cx> fn(&'cx mut ExtCtxt<'_>, Span, ArenaTokenStream) -> MacroExpanderResult<'cx>; impl TTMacroExpander for F where - F: for<'cx> Fn(&'cx mut ExtCtxt<'_>, Span, TokenStream) -> MacroExpanderResult<'cx>, + F: for<'cx> Fn(&'cx mut ExtCtxt<'_>, Span, ArenaTokenStream) -> MacroExpanderResult<'cx>, { fn expand<'cx, 'a: 'cx>( &'a self, @@ -401,7 +401,7 @@ where span: Span, input: ArenaTokenStream, ) -> MacroExpanderResult<'cx> { - self(ecx, span, input.to_token_stream()) + self(ecx, span, input) } } diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs index d20f67cc0f254..2a869dd2eac39 100644 --- a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs +++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs @@ -78,7 +78,7 @@ declare_lint! { declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]); impl Expr2024 { - fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: ArenaTokenTreeIter) { + fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: ArenaTokenTreeIter<'_>) { let mut prev_colon = false; let mut prev_identifier = false; let mut prev_dollar = false; diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index d179b5f56718c..c6964d37ed446 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -29,9 +29,9 @@ pub use path::PathStyle; use rustc_ast::token::{ self, IdentIsRaw, InvisibleOrigin, MetaVarKind, NtExprKind, NtPatKind, Token, TokenKind, }; -use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenTree}; +use rustc_ast::tokenarena::{ArenaTokenStream, ArenaTokenStreamBuilder, ArenaTokenTree}; use rustc_ast::tokenstream::{ - ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, WithTokens, + ParserRange, ParserReplacement, Spacing, TokenCursor, TokenTree, WithTokens, }; use rustc_ast::util::case::Case; use rustc_ast::util::classify; @@ -723,21 +723,21 @@ impl<'a> Parser<'a> { fn check_const_closure(&self) -> bool { self.is_keyword_ahead(0, &[kw::Const]) && self.look_ahead(1, |t| match &t.kind { - // async closures do not work with const closures, so we do not parse that here. - token::Ident(kw::Move | kw::Use | kw::Static, IdentIsRaw::No) - | token::OrOr - | token::Or => true, - _ => false, - }) + // async closures do not work with const closures, so we do not parse that here. + token::Ident(kw::Move | kw::Use | kw::Static, IdentIsRaw::No) + | token::OrOr + | token::Or => true, + _ => false, + }) } fn check_inline_const(&self, dist: usize) -> bool { self.is_keyword_ahead(dist, &[kw::Const]) && self.look_ahead(dist + 1, |t| match &t.kind { - token::OpenBrace => true, - token::OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Block)) => true, - _ => false, - }) + token::OpenBrace => true, + token::OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Block)) => true, + _ => false, + }) } /// Checks to see if the next token is either `+` or `+=`. @@ -1241,7 +1241,7 @@ impl<'a> Parser<'a> { } else { None } - .map(|(kind, span)| CoroutineMarker::new(kind, span)) + .map(|(kind, span)| CoroutineMarker::new(kind, span)) } /// Parses fn unsafety: `unsafe`, `safe` or nothing. @@ -1392,7 +1392,10 @@ impl<'a> Parser<'a> { DelimArgs { dspan: data.span, delim: data.delimiter, - tokens: ArenaTokenStream::separate_delimited_inner(bounds, &self.token_cursor.stream), + tokens: ArenaTokenStream::separate_delimited_inner( + bounds, + &self.token_cursor.stream, + ), } }) } @@ -1443,18 +1446,18 @@ impl<'a> Parser<'a> { } } - pub fn parse_tokens(&mut self) -> TokenStream { - let mut result = Vec::new(); + pub fn parse_tokens(&mut self) -> ArenaTokenStream { + let mut builder = ArenaTokenStreamBuilder::default(); loop { if self.token.kind.is_close_delim_or_eof() { break; } else { - result.push(self.parse_token_tree()); + builder.push_token_tree( + &self.parse_token_tree().to_token_tree(&self.token_cursor.stream), + ); } } - TokenStream::new( - result.into_iter().map(|tt| tt.to_token_tree(&self.token_cursor.stream)).collect(), - ) + builder.finish() } /// Evaluates the closure with restrictions in place.