Skip to content

Commit 58fa03a

Browse files
authored
Rollup merge of rust-lang#62956 - ia0:fix_62831, r=petrochenkov
Implement slow-path for FirstSets::first When 2 or more sequences share the same span, we can't use the precomputed map for their first set. So we compute it recursively. Fixes rust-lang#62831.
2 parents 51028f9 + df4b23e commit 58fa03a

File tree

4 files changed

+119
-27
lines changed

4 files changed

+119
-27
lines changed

src/libsyntax/ext/tt/macro_rules.rs

+26-27
Original file line numberDiff line numberDiff line change
@@ -625,38 +625,37 @@ impl FirstSets {
625625
return first;
626626
}
627627
TokenTree::Sequence(sp, ref seq_rep) => {
628-
match self.first.get(&sp.entire()) {
629-
Some(&Some(ref subfirst)) => {
630-
// If the sequence contents can be empty, then the first
631-
// token could be the separator token itself.
632-
633-
if let (Some(sep), true) = (&seq_rep.separator, subfirst.maybe_empty) {
634-
first.add_one_maybe(TokenTree::Token(sep.clone()));
635-
}
636-
637-
assert!(first.maybe_empty);
638-
first.add_all(subfirst);
639-
if subfirst.maybe_empty
640-
|| seq_rep.kleene.op == quoted::KleeneOp::ZeroOrMore
641-
|| seq_rep.kleene.op == quoted::KleeneOp::ZeroOrOne
642-
{
643-
// continue scanning for more first
644-
// tokens, but also make sure we
645-
// restore empty-tracking state
646-
first.maybe_empty = true;
647-
continue;
648-
} else {
649-
return first;
650-
}
651-
}
652-
628+
let subfirst_owned;
629+
let subfirst = match self.first.get(&sp.entire()) {
630+
Some(&Some(ref subfirst)) => subfirst,
653631
Some(&None) => {
654-
panic!("assume all sequences have (unique) spans for now");
632+
subfirst_owned = self.first(&seq_rep.tts[..]);
633+
&subfirst_owned
655634
}
656-
657635
None => {
658636
panic!("We missed a sequence during FirstSets construction");
659637
}
638+
};
639+
640+
// If the sequence contents can be empty, then the first
641+
// token could be the separator token itself.
642+
if let (Some(sep), true) = (&seq_rep.separator, subfirst.maybe_empty) {
643+
first.add_one_maybe(TokenTree::Token(sep.clone()));
644+
}
645+
646+
assert!(first.maybe_empty);
647+
first.add_all(subfirst);
648+
if subfirst.maybe_empty
649+
|| seq_rep.kleene.op == quoted::KleeneOp::ZeroOrMore
650+
|| seq_rep.kleene.op == quoted::KleeneOp::ZeroOrOne
651+
{
652+
// Continue scanning for more first
653+
// tokens, but also make sure we
654+
// restore empty-tracking state.
655+
first.maybe_empty = true;
656+
continue;
657+
} else {
658+
return first;
660659
}
661660
}
662661
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// force-host
2+
// no-prefer-dynamic
3+
4+
#![crate_type = "proc-macro"]
5+
#![feature(proc_macro_span, proc_macro_hygiene, proc_macro_quote)]
6+
7+
extern crate proc_macro;
8+
9+
use proc_macro::{quote, Span, TokenStream};
10+
11+
fn assert_same_span(a: Span, b: Span) {
12+
assert_eq!(a.start(), b.start());
13+
assert_eq!(a.end(), b.end());
14+
}
15+
16+
// This macro generates a macro with the same macro definition as `manual_foo` in
17+
// `same-sequence-span.rs` but with the same span for all sequences.
18+
#[proc_macro]
19+
pub fn make_foo(_: TokenStream) -> TokenStream {
20+
let result = quote! {
21+
macro_rules! generated_foo {
22+
(1 $$x:expr $$($$y:tt,)* $$(= $$z:tt)*) => {};
23+
}
24+
};
25+
26+
// Check that all spans are equal.
27+
let mut span = None;
28+
for tt in result.clone() {
29+
match span {
30+
None => span = Some(tt.span()),
31+
Some(span) => assert_same_span(tt.span(), span),
32+
}
33+
}
34+
35+
result
36+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// aux-build:proc_macro_sequence.rs
2+
3+
// Regression test for issue #62831: Check that multiple sequences with the same span in the
4+
// left-hand side of a macro definition behave as if they had unique spans, and in particular that
5+
// they don't crash the compiler.
6+
7+
#![feature(proc_macro_hygiene)]
8+
#![allow(unused_macros)]
9+
10+
extern crate proc_macro_sequence;
11+
12+
// When ignoring spans, this macro has the same macro definition as `generated_foo` in
13+
// `proc_macro_sequence.rs`.
14+
macro_rules! manual_foo {
15+
(1 $x:expr $($y:tt,)* //~ERROR `$x:expr` may be followed by `$y:tt`
16+
$(= $z:tt)* //~ERROR `$x:expr` may be followed by `=`
17+
) => {};
18+
}
19+
20+
proc_macro_sequence::make_foo!(); //~ERROR `$x:expr` may be followed by `$y:tt`
21+
//~^ERROR `$x:expr` may be followed by `=`
22+
23+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error: `$x:expr` may be followed by `$y:tt`, which is not allowed for `expr` fragments
2+
--> $DIR/same-sequence-span.rs:15:18
3+
|
4+
LL | (1 $x:expr $($y:tt,)*
5+
| ^^^^^ not allowed after `expr` fragments
6+
|
7+
= note: allowed there are: `=>`, `,` or `;`
8+
9+
error: `$x:expr` may be followed by `=`, which is not allowed for `expr` fragments
10+
--> $DIR/same-sequence-span.rs:16:18
11+
|
12+
LL | $(= $z:tt)*
13+
| ^ not allowed after `expr` fragments
14+
|
15+
= note: allowed there are: `=>`, `,` or `;`
16+
17+
error: `$x:expr` may be followed by `$y:tt`, which is not allowed for `expr` fragments
18+
--> $DIR/same-sequence-span.rs:20:1
19+
|
20+
LL | proc_macro_sequence::make_foo!();
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed after `expr` fragments
22+
|
23+
= note: allowed there are: `=>`, `,` or `;`
24+
25+
error: `$x:expr` may be followed by `=`, which is not allowed for `expr` fragments
26+
--> $DIR/same-sequence-span.rs:20:1
27+
|
28+
LL | proc_macro_sequence::make_foo!();
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed after `expr` fragments
30+
|
31+
= note: allowed there are: `=>`, `,` or `;`
32+
33+
error: aborting due to 4 previous errors
34+

0 commit comments

Comments
 (0)