Skip to content

Commit f94360f

Browse files
committed
Always preserve None-delimited groups in a captured TokenStream
Previously, we would silently remove any `None`-delimiters when capturing a `TokenStream`, 'flattenting' them to their inner tokens. This was not normally visible, since we usually have `TokenKind::Interpolated` (which gets converted to a `None`-delimited group during macro invocation) instead of an actual `None`-delimited group. However, there are a couple of cases where this becomes visible to proc-macros: 1. A cross-crate `macro_rules!` macro has a `None`-delimited group stored in its body (as a result of being produced by another `macro_rules!` macro). The cross-crate `macro_rules!` invocation can then expand to an attribute macro invocation, which needs to be able to see the `None`-delimited group. 2. A proc-macro can invoke an attribute proc-macro with its re-collected input. If there are any nonterminals present in the input, they will get re-collected to `None`-delimited groups, which will then get captured as part of the attribute macro invocation. Both of these cases are incredibly obscure, so there hopefully won't be any breakage. This change will allow more agressive 'flattenting' of nonterminals in #82608 without losing `None`-delimited groups.
1 parent f811f14 commit f94360f

File tree

5 files changed

+113
-28
lines changed

5 files changed

+113
-28
lines changed

compiler/rustc_parse/src/parser/attr_wrapper.rs

+35-10
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,46 @@ impl<'a> Parser<'a> {
9898
}
9999
impl CreateTokenStream for LazyTokenStreamImpl {
100100
fn create_token_stream(&self) -> TokenStream {
101-
// The token produced by the final call to `next` or `next_desugared`
102-
// was not actually consumed by the callback. The combination
103-
// of chaining the initial token and using `take` produces the desired
104-
// result - we produce an empty `TokenStream` if no calls were made,
105-
// and omit the final token otherwise.
101+
if self.num_calls == 0 {
102+
return TokenStream::new(vec![]);
103+
}
104+
106105
let mut cursor_snapshot = self.cursor_snapshot.clone();
107-
let tokens = std::iter::once(self.start_token.clone())
108-
.chain((0..self.num_calls).map(|_| {
109-
if self.desugar_doc_comments {
106+
// Don't skip `None` delimiters, since we want to pass them to
107+
// proc macros. Normally, we'll end up capturing `TokenKind::Interpolated`,
108+
// which gets converted to a `None`-delimited group when we invoke
109+
// a proc-macro. However, it's possible to already have a `None`-delimited
110+
// group in the stream (such as when parsing the output of a proc-macro,
111+
// or in certain unusual cases with cross-crate `macro_rules!` macros).
112+
cursor_snapshot.skip_none_delims = false;
113+
114+
// The token produced by the final call to `next` or `next_desugared`
115+
// was not actually consumed by the callback.
116+
let num_calls = self.num_calls - 1;
117+
let mut i = 0;
118+
let tokens =
119+
std::iter::once(self.start_token.clone()).chain(std::iter::from_fn(|| {
120+
if i >= num_calls {
121+
return None;
122+
}
123+
124+
let token = if self.desugar_doc_comments {
110125
cursor_snapshot.next_desugared()
111126
} else {
112127
cursor_snapshot.next()
128+
};
129+
130+
// When the `LazyTokenStreamImpl` was original produced, we did *not*
131+
// include `NoDelim` tokens in `num_calls`, since they are normally ignored
132+
// by the parser. Therefore, we only increment our counter for other types of tokens.
133+
if !matches!(
134+
token.0.kind,
135+
token::OpenDelim(token::NoDelim) | token::CloseDelim(token::NoDelim)
136+
) {
137+
i += 1;
113138
}
114-
}))
115-
.take(self.num_calls);
139+
Some(token)
140+
}));
116141

117142
make_token_stream(tokens, self.append_unglued_token.clone())
118143
}

compiler/rustc_parse/src/parser/mod.rs

+20-5
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ struct TokenCursor {
172172
// appended to the captured stream when
173173
// we evaluate a `LazyTokenStream`
174174
append_unglued_token: Option<TreeAndSpacing>,
175+
// If `true`, skip the delimiters for `None`-delimited groups,
176+
// and just yield the inner tokens. This is `true` during
177+
// normal parsing, since the parser code is not currently prepared
178+
// to handle `None` delimiters. When capturing a `TokenStream`,
179+
// however, we want to handle `None`-delimiters, since
180+
// proc-macros always see `None`-delimited groups.
181+
skip_none_delims: bool,
175182
}
176183

177184
#[derive(Clone)]
@@ -184,13 +191,13 @@ struct TokenCursorFrame {
184191
}
185192

186193
impl TokenCursorFrame {
187-
fn new(span: DelimSpan, delim: DelimToken, tts: TokenStream) -> Self {
194+
fn new(span: DelimSpan, delim: DelimToken, tts: TokenStream, skip_none_delims: bool) -> Self {
188195
TokenCursorFrame {
189196
delim,
190197
span,
191-
open_delim: delim == token::NoDelim,
198+
open_delim: delim == token::NoDelim && skip_none_delims,
192199
tree_cursor: tts.into_trees(),
193-
close_delim: delim == token::NoDelim,
200+
close_delim: delim == token::NoDelim && skip_none_delims,
194201
}
195202
}
196203
}
@@ -218,7 +225,7 @@ impl TokenCursor {
218225
return (token, spacing);
219226
}
220227
TokenTree::Delimited(sp, delim, tts) => {
221-
let frame = TokenCursorFrame::new(sp, delim, tts);
228+
let frame = TokenCursorFrame::new(sp, delim, tts, self.skip_none_delims);
222229
self.stack.push(mem::replace(&mut self.frame, frame));
223230
}
224231
}
@@ -276,6 +283,7 @@ impl TokenCursor {
276283
.cloned()
277284
.collect::<TokenStream>()
278285
},
286+
self.skip_none_delims,
279287
),
280288
));
281289

@@ -371,12 +379,19 @@ impl<'a> Parser<'a> {
371379
prev_token: Token::dummy(),
372380
restrictions: Restrictions::empty(),
373381
expected_tokens: Vec::new(),
382+
// Skip over the delimiters for `None`-delimited groups
374383
token_cursor: TokenCursor {
375-
frame: TokenCursorFrame::new(DelimSpan::dummy(), token::NoDelim, tokens),
384+
frame: TokenCursorFrame::new(
385+
DelimSpan::dummy(),
386+
token::NoDelim,
387+
tokens,
388+
/* skip_none_delims */ true,
389+
),
376390
stack: Vec::new(),
377391
num_next_calls: 0,
378392
desugar_doc_comments,
379393
append_unglued_token: None,
394+
skip_none_delims: true,
380395
},
381396
desugar_doc_comments,
382397
unmatched_angle_bracket_count: 0,

src/test/ui/proc-macro/auxiliary/nested-macro-rules.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ pub struct FirstStruct;
22

33
#[macro_export]
44
macro_rules! outer_macro {
5-
($name:ident) => {
5+
($name:ident, $attr_struct_name:ident) => {
66
#[macro_export]
77
macro_rules! inner_macro {
8-
($wrapper:ident) => {
9-
$wrapper!($name)
8+
($bang_macro:ident, $attr_macro:ident) => {
9+
$bang_macro!($name);
10+
#[$attr_macro] struct $attr_struct_name {}
1011
}
1112
}
1213
}
1314
}
1415

15-
outer_macro!(FirstStruct);
16+
outer_macro!(FirstStruct, FirstAttrStruct);
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// run-pass
22
// aux-build:nested-macro-rules.rs
33
// aux-build:test-macros.rs
4-
// compile-flags: -Z span-debug
4+
// compile-flags: -Z span-debug -Z macro-backtrace
55
// edition:2018
66

77
#![no_std] // Don't load unnecessary hygiene information from std
@@ -10,14 +10,14 @@ extern crate std;
1010
extern crate nested_macro_rules;
1111
extern crate test_macros;
1212

13-
use test_macros::print_bang;
13+
use test_macros::{print_bang, print_attr};
1414

1515
use nested_macro_rules::FirstStruct;
1616
struct SecondStruct;
1717

1818
fn main() {
19-
nested_macro_rules::inner_macro!(print_bang);
19+
nested_macro_rules::inner_macro!(print_bang, print_attr);
2020

21-
nested_macro_rules::outer_macro!(SecondStruct);
22-
inner_macro!(print_bang);
21+
nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
22+
inner_macro!(print_bang, print_attr);
2323
}

src/test/ui/proc-macro/nested-macro-rules.stdout

+48-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,32 @@ PRINT-BANG INPUT (DEBUG): TokenStream [
55
stream: TokenStream [
66
Ident {
77
ident: "FirstStruct",
8-
span: $DIR/auxiliary/nested-macro-rules.rs:15:14: 15:25 (#7),
8+
span: $DIR/auxiliary/nested-macro-rules.rs:16:14: 16:25 (#7),
99
},
1010
],
11-
span: $DIR/auxiliary/nested-macro-rules.rs:9:27: 9:32 (#6),
11+
span: $DIR/auxiliary/nested-macro-rules.rs:9:30: 9:35 (#6),
12+
},
13+
]
14+
PRINT-ATTR INPUT (DISPLAY): struct FirstAttrStruct { }
15+
PRINT-ATTR INPUT (DEBUG): TokenStream [
16+
Ident {
17+
ident: "struct",
18+
span: $DIR/auxiliary/nested-macro-rules.rs:10:32: 10:38 (#6),
19+
},
20+
Group {
21+
delimiter: None,
22+
stream: TokenStream [
23+
Ident {
24+
ident: "FirstAttrStruct",
25+
span: $DIR/auxiliary/nested-macro-rules.rs:16:27: 16:42 (#7),
26+
},
27+
],
28+
span: $DIR/auxiliary/nested-macro-rules.rs:10:39: 10:56 (#6),
29+
},
30+
Group {
31+
delimiter: Brace,
32+
stream: TokenStream [],
33+
span: $DIR/auxiliary/nested-macro-rules.rs:10:57: 10:59 (#6),
1234
},
1335
]
1436
PRINT-BANG INPUT (DISPLAY): SecondStruct
@@ -18,9 +40,31 @@ PRINT-BANG INPUT (DEBUG): TokenStream [
1840
stream: TokenStream [
1941
Ident {
2042
ident: "SecondStruct",
21-
span: $DIR/nested-macro-rules.rs:21:38: 21:50 (#13),
43+
span: $DIR/nested-macro-rules.rs:21:38: 21:50 (#16),
2244
},
2345
],
24-
span: $DIR/auxiliary/nested-macro-rules.rs:9:27: 9:32 (#12),
46+
span: $DIR/auxiliary/nested-macro-rules.rs:9:30: 9:35 (#15),
47+
},
48+
]
49+
PRINT-ATTR INPUT (DISPLAY): struct SecondAttrStruct { }
50+
PRINT-ATTR INPUT (DEBUG): TokenStream [
51+
Ident {
52+
ident: "struct",
53+
span: $DIR/auxiliary/nested-macro-rules.rs:10:32: 10:38 (#15),
54+
},
55+
Group {
56+
delimiter: None,
57+
stream: TokenStream [
58+
Ident {
59+
ident: "SecondAttrStruct",
60+
span: $DIR/nested-macro-rules.rs:21:52: 21:68 (#16),
61+
},
62+
],
63+
span: $DIR/auxiliary/nested-macro-rules.rs:10:39: 10:56 (#15),
64+
},
65+
Group {
66+
delimiter: Brace,
67+
stream: TokenStream [],
68+
span: $DIR/auxiliary/nested-macro-rules.rs:10:57: 10:59 (#15),
2569
},
2670
]

0 commit comments

Comments
 (0)