Skip to content

Commit 097e262

Browse files
committed
Auto merge of #60932 - Centril:macro-at-most-once-2015, r=<try>
Support ? Kleene macro operator in 2015 Closes #56668. See that issue for rationale and discussion. Crater will be needed and then, if all goes well, FCP.
2 parents 548add7 + 695b601 commit 097e262

13 files changed

+189
-254
lines changed

src/libsyntax/ext/tt/quoted.rs

+4-167
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::ast::NodeId;
2-
use crate::early_buffered_lints::BufferedEarlyLintId;
32
use crate::ext::tt::macro_parser;
43
use crate::feature_gate::Features;
54
use crate::parse::{token, ParseSess};
@@ -291,16 +290,7 @@ where
291290
macro_node_id,
292291
);
293292
// Get the Kleene operator and optional separator
294-
let (separator, op) =
295-
parse_sep_and_kleene_op(
296-
trees,
297-
span.entire(),
298-
sess,
299-
features,
300-
attrs,
301-
edition,
302-
macro_node_id,
303-
);
293+
let (separator, op) = parse_sep_and_kleene_op(trees, span.entire(), sess);
304294
// Count the number of captured "names" (i.e., named metavars)
305295
let name_captures = macro_parser::count_names(&sequence);
306296
TokenTree::Sequence(
@@ -411,164 +401,11 @@ where
411401
/// session `sess`. If the next one (or possibly two) tokens in `input` correspond to a Kleene
412402
/// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an
413403
/// error with the appropriate span is emitted to `sess` and a dummy value is returned.
414-
///
415-
/// N.B., in the 2015 edition, `*` and `+` are the only Kleene operators, and `?` is a separator.
416-
/// In the 2018 edition however, `?` is a Kleene operator, and not a separator.
417-
fn parse_sep_and_kleene_op<I>(
418-
input: &mut Peekable<I>,
419-
span: Span,
420-
sess: &ParseSess,
421-
features: &Features,
422-
attrs: &[ast::Attribute],
423-
edition: Edition,
424-
macro_node_id: NodeId,
425-
) -> (Option<token::Token>, KleeneOp)
426-
where
427-
I: Iterator<Item = tokenstream::TokenTree>,
428-
{
429-
match edition {
430-
Edition::Edition2015 => parse_sep_and_kleene_op_2015(
431-
input,
432-
span,
433-
sess,
434-
features,
435-
attrs,
436-
macro_node_id,
437-
),
438-
Edition::Edition2018 => parse_sep_and_kleene_op_2018(input, span, sess, features, attrs),
439-
}
440-
}
441-
442-
// `?` is a separator (with a migration warning) and never a KleeneOp.
443-
fn parse_sep_and_kleene_op_2015<I>(
444-
input: &mut Peekable<I>,
445-
span: Span,
446-
sess: &ParseSess,
447-
_features: &Features,
448-
_attrs: &[ast::Attribute],
449-
macro_node_id: NodeId,
450-
) -> (Option<token::Token>, KleeneOp)
451-
where
452-
I: Iterator<Item = tokenstream::TokenTree>,
453-
{
454-
// We basically look at two token trees here, denoted as #1 and #2 below
455-
let span = match parse_kleene_op(input, span) {
456-
// #1 is a `+` or `*` KleeneOp
457-
//
458-
// `?` is ambiguous: it could be a separator (warning) or a Kleene::ZeroOrOne (error), so
459-
// we need to look ahead one more token to be sure.
460-
Ok(Ok((op, _))) if op != KleeneOp::ZeroOrOne => return (None, op),
461-
462-
// #1 is `?` token, but it could be a Kleene::ZeroOrOne (error in 2015) without a separator
463-
// or it could be a `?` separator followed by any Kleene operator. We need to look ahead 1
464-
// token to find out which.
465-
Ok(Ok((op, op1_span))) => {
466-
assert_eq!(op, KleeneOp::ZeroOrOne);
467-
468-
// Lookahead at #2. If it is a KleenOp, then #1 is a separator.
469-
let is_1_sep = if let Some(&tokenstream::TokenTree::Token(_, ref tok2)) = input.peek() {
470-
kleene_op(tok2).is_some()
471-
} else {
472-
false
473-
};
474-
475-
if is_1_sep {
476-
// #1 is a separator and #2 should be a KleepeOp.
477-
// (N.B. We need to advance the input iterator.)
478-
match parse_kleene_op(input, span) {
479-
// #2 is `?`, which is not allowed as a Kleene op in 2015 edition,
480-
// but is allowed in the 2018 edition.
481-
Ok(Ok((op, op2_span))) if op == KleeneOp::ZeroOrOne => {
482-
sess.span_diagnostic
483-
.struct_span_err(op2_span, "expected `*` or `+`")
484-
.note("`?` is not a macro repetition operator in the 2015 edition, \
485-
but is accepted in the 2018 edition")
486-
.emit();
487-
488-
// Return a dummy
489-
return (None, KleeneOp::ZeroOrMore);
490-
}
491-
492-
// #2 is a Kleene op, which is the only valid option
493-
Ok(Ok((op, _))) => {
494-
// Warn that `?` as a separator will be deprecated
495-
sess.buffer_lint(
496-
BufferedEarlyLintId::QuestionMarkMacroSep,
497-
op1_span,
498-
macro_node_id,
499-
"using `?` as a separator is deprecated and will be \
500-
a hard error in an upcoming edition",
501-
);
502-
503-
return (Some(token::Question), op);
504-
}
505-
506-
// #2 is a random token (this is an error) :(
507-
Ok(Err((_, _))) => op1_span,
508-
509-
// #2 is not even a token at all :(
510-
Err(_) => op1_span,
511-
}
512-
} else {
513-
// `?` is not allowed as a Kleene op in 2015,
514-
// but is allowed in the 2018 edition
515-
sess.span_diagnostic
516-
.struct_span_err(op1_span, "expected `*` or `+`")
517-
.note("`?` is not a macro repetition operator in the 2015 edition, \
518-
but is accepted in the 2018 edition")
519-
.emit();
520-
521-
// Return a dummy
522-
return (None, KleeneOp::ZeroOrMore);
523-
}
524-
}
525-
526-
// #1 is a separator followed by #2, a KleeneOp
527-
Ok(Err((tok, span))) => match parse_kleene_op(input, span) {
528-
// #2 is a `?`, which is not allowed as a Kleene op in 2015 edition,
529-
// but is allowed in the 2018 edition
530-
Ok(Ok((op, op2_span))) if op == KleeneOp::ZeroOrOne => {
531-
sess.span_diagnostic
532-
.struct_span_err(op2_span, "expected `*` or `+`")
533-
.note("`?` is not a macro repetition operator in the 2015 edition, \
534-
but is accepted in the 2018 edition")
535-
.emit();
536-
537-
// Return a dummy
538-
return (None, KleeneOp::ZeroOrMore);
539-
}
540-
541-
// #2 is a KleeneOp :D
542-
Ok(Ok((op, _))) => return (Some(tok), op),
543-
544-
// #2 is a random token :(
545-
Ok(Err((_, span))) => span,
546-
547-
// #2 is not a token at all :(
548-
Err(span) => span,
549-
},
550-
551-
// #1 is not a token
552-
Err(span) => span,
553-
};
554-
555-
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
556-
557-
// Return a dummy
558-
(None, KleeneOp::ZeroOrMore)
559-
}
560-
561-
// `?` is a Kleene op, not a separator
562-
fn parse_sep_and_kleene_op_2018<I>(
563-
input: &mut Peekable<I>,
404+
fn parse_sep_and_kleene_op(
405+
input: &mut Peekable<impl Iterator<Item = tokenstream::TokenTree>>,
564406
span: Span,
565407
sess: &ParseSess,
566-
_features: &Features,
567-
_attrs: &[ast::Attribute],
568-
) -> (Option<token::Token>, KleeneOp)
569-
where
570-
I: Iterator<Item = tokenstream::TokenTree>,
571-
{
408+
) -> (Option<token::Token>, KleeneOp) {
572409
// We basically look at two token trees here, denoted as #1 and #2 below
573410
let span = match parse_kleene_op(input, span) {
574411
// #1 is a `?` (needs feature gate)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// run-pass
2+
#![allow(unused_mut)]
3+
// The logic for parsing Kleene operators in macros has a special case to disambiguate `?`.
4+
// Specifically, `$(pat)?` is the ZeroOrOne operator whereas `$(pat)?+` or `$(pat)?*` are the
5+
// ZeroOrMore and OneOrMore operators using `?` as a separator. These tests are intended to
6+
// exercise that logic in the macro parser.
7+
//
8+
// Moreover, we also throw in some tests for using a separator with `?`, which is meaningless but
9+
// included for consistency with `+` and `*`.
10+
//
11+
// This test focuses on non-error cases and making sure the correct number of repetitions happen.
12+
13+
// edition:2015
14+
15+
macro_rules! foo {
16+
($($a:ident)? ; $num:expr) => { {
17+
let mut x = 0;
18+
19+
$(
20+
x += $a;
21+
)?
22+
23+
assert_eq!(x, $num);
24+
} }
25+
}
26+
27+
pub fn main() {
28+
let a = 1;
29+
30+
// accept 0 or 1 repetitions
31+
foo!( ; 0);
32+
foo!(a ; 1);
33+
}

src/test/ui/issues/issue-39388.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(unused_macros)]
22

33
macro_rules! assign {
4-
(($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected `*` or `+`
4+
(($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected one of: `*`, `+`, or `?`
55
$($a)* = $($b)*
66
}
77
}

src/test/ui/issues/issue-39388.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: expected `*` or `+`
1+
error: expected one of: `*`, `+`, or `?`
22
--> $DIR/issue-39388.rs:4:22
33
|
44
LL | (($($a:tt)*) = ($($b:tt))*) => {

src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.rs

-13
This file was deleted.

src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.stderr

-18
This file was deleted.

src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.rs

-28
This file was deleted.

src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.stderr

-24
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Tests that `?` is a Kleene op and not a macro separator in the 2015 edition.
2+
3+
// edition:2015
4+
5+
macro_rules! foo {
6+
($(a)?) => {};
7+
}
8+
9+
macro_rules! baz {
10+
($(a),?) => {}; //~ERROR the `?` macro repetition operator
11+
}
12+
13+
macro_rules! barplus {
14+
($(a)?+) => {}; // ok. matches "a+" and "+"
15+
}
16+
17+
macro_rules! barstar {
18+
($(a)?*) => {}; // ok. matches "a*" and "*"
19+
}
20+
21+
pub fn main() {
22+
foo!();
23+
foo!(a);
24+
foo!(a?); //~ ERROR no rules expected the token `?`
25+
foo!(a?a); //~ ERROR no rules expected the token `?`
26+
foo!(a?a?a); //~ ERROR no rules expected the token `?`
27+
28+
barplus!(); //~ERROR unexpected end of macro invocation
29+
barplus!(a); //~ERROR unexpected end of macro invocation
30+
barplus!(a?); //~ ERROR no rules expected the token `?`
31+
barplus!(a?a); //~ ERROR no rules expected the token `?`
32+
barplus!(a+);
33+
barplus!(+);
34+
35+
barstar!(); //~ERROR unexpected end of macro invocation
36+
barstar!(a); //~ERROR unexpected end of macro invocation
37+
barstar!(a?); //~ ERROR no rules expected the token `?`
38+
barstar!(a?a); //~ ERROR no rules expected the token `?`
39+
barstar!(a*);
40+
barstar!(*);
41+
}

0 commit comments

Comments
 (0)