Auto merge of #47752 - mark-i-m:at-most-once-rep, r=nikomatsakis
Implement `?` macro repetition See rust-lang/rfcs#2298 (with disposition merge)
This commit is contained in:
commit
b8398d947d
@ -0,0 +1,17 @@
|
||||
# `macro_at_most_once_rep`
|
||||
|
||||
The tracking issue for this feature is: TODO(mark-i-m)
|
||||
|
||||
With this feature gate enabled, one can use `?` as a Kleene operator meaning "0
|
||||
or 1 repetitions" in a macro definition. Previously only `+` and `*` were allowed.
|
||||
|
||||
For example:
|
||||
```rust
|
||||
macro_rules! foo {
|
||||
(something $(,)?) // `?` indicates `,` is "optional"...
|
||||
=> {}
|
||||
}
|
||||
```
|
||||
|
||||
------------------------
|
||||
|
@ -181,6 +181,8 @@ struct MatcherPos {
|
||||
match_hi: usize,
|
||||
|
||||
// Specifically used if we are matching a repetition. If we aren't both should be `None`.
|
||||
/// The KleeneOp of this sequence if we are in a repetition.
|
||||
seq_op: Option<quoted::KleeneOp>,
|
||||
/// The separator if we are in a repetition
|
||||
sep: Option<Token>,
|
||||
/// The "parent" matcher position if we are in a repetition. That is, the matcher position just
|
||||
@ -263,6 +265,7 @@ fn initial_matcher_pos(ms: Vec<TokenTree>, lo: BytePos) -> Box<MatcherPos> {
|
||||
stack: vec![],
|
||||
|
||||
// Haven't descended into any sequences, so both of these are `None`.
|
||||
seq_op: None,
|
||||
sep: None,
|
||||
up: None,
|
||||
})
|
||||
@ -466,8 +469,8 @@ fn inner_parse_loop(
|
||||
}
|
||||
}
|
||||
// We don't need a separator. Move the "dot" back to the beginning of the matcher
|
||||
// and try to match again.
|
||||
else {
|
||||
// and try to match again UNLESS we are only allowed to have _one_ repetition.
|
||||
else if item.seq_op != Some(quoted::KleeneOp::ZeroOrOne) {
|
||||
item.match_cur = item.match_lo;
|
||||
item.idx = 0;
|
||||
cur_items.push(item);
|
||||
@ -486,8 +489,10 @@ fn inner_parse_loop(
|
||||
match item.top_elts.get_tt(idx) {
|
||||
// Need to descend into a sequence
|
||||
TokenTree::Sequence(sp, seq) => {
|
||||
if seq.op == quoted::KleeneOp::ZeroOrMore {
|
||||
// Examine the case where there are 0 matches of this sequence
|
||||
// Examine the case where there are 0 matches of this sequence
|
||||
if seq.op == quoted::KleeneOp::ZeroOrMore
|
||||
|| seq.op == quoted::KleeneOp::ZeroOrOne
|
||||
{
|
||||
let mut new_item = item.clone();
|
||||
new_item.match_cur += seq.num_captures;
|
||||
new_item.idx += 1;
|
||||
@ -497,11 +502,11 @@ fn inner_parse_loop(
|
||||
cur_items.push(new_item);
|
||||
}
|
||||
|
||||
// Examine the case where there is at least one match of this sequence
|
||||
let matches = create_matches(item.matches.len());
|
||||
cur_items.push(Box::new(MatcherPos {
|
||||
stack: vec![],
|
||||
sep: seq.separator.clone(),
|
||||
seq_op: Some(seq.op),
|
||||
idx: 0,
|
||||
matches,
|
||||
match_lo: item.match_cur,
|
||||
|
@ -237,7 +237,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
|
||||
s.iter().map(|m| {
|
||||
if let MatchedNonterminal(ref nt) = *m {
|
||||
if let NtTT(ref tt) = **nt {
|
||||
let tt = quoted::parse(tt.clone().into(), true, sess).pop().unwrap();
|
||||
let tt = quoted::parse(tt.clone().into(), true, sess, features, &def.attrs)
|
||||
.pop().unwrap();
|
||||
valid &= check_lhs_nt_follows(sess, features, &def.attrs, &tt);
|
||||
return tt;
|
||||
}
|
||||
@ -253,7 +254,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
|
||||
s.iter().map(|m| {
|
||||
if let MatchedNonterminal(ref nt) = *m {
|
||||
if let NtTT(ref tt) = **nt {
|
||||
return quoted::parse(tt.clone().into(), false, sess).pop().unwrap();
|
||||
return quoted::parse(tt.clone().into(), false, sess, features, &def.attrs)
|
||||
.pop().unwrap();
|
||||
}
|
||||
}
|
||||
sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")
|
||||
|
@ -8,14 +8,17 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use ast;
|
||||
use {ast, attr};
|
||||
use ext::tt::macro_parser;
|
||||
use feature_gate::{self, emit_feature_err, Features, GateIssue};
|
||||
use parse::{token, ParseSess};
|
||||
use print::pprust;
|
||||
use symbol::keywords;
|
||||
use syntax_pos::{BytePos, Span, DUMMY_SP};
|
||||
use tokenstream;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::iter::Peekable;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note
|
||||
@ -78,6 +81,7 @@ pub enum KleeneOp {
|
||||
ZeroOrMore,
|
||||
/// Kleene plus (`+`) for one or more repetitions
|
||||
OneOrMore,
|
||||
ZeroOrOne,
|
||||
}
|
||||
|
||||
/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)`
|
||||
@ -169,6 +173,8 @@ impl TokenTree {
|
||||
/// `ident` are "matchers". They are not present in the body of a macro rule -- just in the
|
||||
/// pattern, so we pass a parameter to indicate whether to expect them or not.
|
||||
/// - `sess`: the parsing session. Any errors will be emitted to this session.
|
||||
/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use
|
||||
/// unstable features or not.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
@ -177,18 +183,19 @@ pub fn parse(
|
||||
input: tokenstream::TokenStream,
|
||||
expect_matchers: bool,
|
||||
sess: &ParseSess,
|
||||
features: &RefCell<Features>,
|
||||
attrs: &[ast::Attribute],
|
||||
) -> Vec<TokenTree> {
|
||||
// Will contain the final collection of `self::TokenTree`
|
||||
let mut result = Vec::new();
|
||||
|
||||
// For each token tree in `input`, parse the token into a `self::TokenTree`, consuming
|
||||
// additional trees if need be.
|
||||
let mut trees = input.trees();
|
||||
let mut trees = input.trees().peekable();
|
||||
while let Some(tree) = trees.next() {
|
||||
let tree = parse_tree(tree, &mut trees, expect_matchers, sess);
|
||||
|
||||
// Given the parsed tree, if there is a metavar and we are expecting matchers, actually
|
||||
// parse out the matcher (i.e. in `$id:ident` this would parse the `:` and `ident`).
|
||||
let tree = parse_tree(tree, &mut trees, expect_matchers, sess, features, attrs);
|
||||
match tree {
|
||||
TokenTree::MetaVar(start_sp, ident) if expect_matchers => {
|
||||
let span = match trees.next() {
|
||||
@ -237,11 +244,15 @@ pub fn parse(
|
||||
/// converting `tree`
|
||||
/// - `expect_matchers`: same as for `parse` (see above).
|
||||
/// - `sess`: the parsing session. Any errors will be emitted to this session.
|
||||
/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use
|
||||
/// unstable features or not.
|
||||
fn parse_tree<I>(
|
||||
tree: tokenstream::TokenTree,
|
||||
trees: &mut I,
|
||||
trees: &mut Peekable<I>,
|
||||
expect_matchers: bool,
|
||||
sess: &ParseSess,
|
||||
features: &RefCell<Features>,
|
||||
attrs: &[ast::Attribute],
|
||||
) -> TokenTree
|
||||
where
|
||||
I: Iterator<Item = tokenstream::TokenTree>,
|
||||
@ -260,9 +271,9 @@ where
|
||||
sess.span_diagnostic.span_err(span, &msg);
|
||||
}
|
||||
// Parse the contents of the sequence itself
|
||||
let sequence = parse(delimited.tts.into(), expect_matchers, sess);
|
||||
let sequence = parse(delimited.tts.into(), expect_matchers, sess, features, attrs);
|
||||
// Get the Kleene operator and optional separator
|
||||
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess);
|
||||
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess, features, attrs);
|
||||
// Count the number of captured "names" (i.e. named metavars)
|
||||
let name_captures = macro_parser::count_names(&sequence);
|
||||
TokenTree::Sequence(
|
||||
@ -315,12 +326,46 @@ where
|
||||
span,
|
||||
Rc::new(Delimited {
|
||||
delim: delimited.delim,
|
||||
tts: parse(delimited.tts.into(), expect_matchers, sess),
|
||||
tts: parse(delimited.tts.into(), expect_matchers, sess, features, attrs),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a token and returns `Some(KleeneOp)` if the token is `+` `*` or `?`. Otherwise, return
|
||||
/// `None`.
|
||||
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
|
||||
match *token {
|
||||
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
|
||||
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
|
||||
token::Question => Some(KleeneOp::ZeroOrOne),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the next token tree of the input looking for a KleeneOp. Returns
|
||||
///
|
||||
/// - Ok(Ok(op)) if the next token tree is a KleeneOp
|
||||
/// - Ok(Err(tok, span)) if the next token tree is a token but not a KleeneOp
|
||||
/// - Err(span) if the next token tree is not a token
|
||||
fn parse_kleene_op<I>(
|
||||
input: &mut I,
|
||||
span: Span,
|
||||
) -> Result<Result<KleeneOp, (token::Token, Span)>, Span>
|
||||
where
|
||||
I: Iterator<Item = tokenstream::TokenTree>,
|
||||
{
|
||||
match input.next() {
|
||||
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
|
||||
Some(op) => Ok(Ok(op)),
|
||||
None => Ok(Err((tok, span))),
|
||||
},
|
||||
tree => Err(tree.as_ref()
|
||||
.map(tokenstream::TokenTree::span)
|
||||
.unwrap_or(span)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to parse a single Kleene star, possibly with a separator.
|
||||
///
|
||||
/// For example, in a pattern such as `$(a),*`, `a` is the pattern to be repeated, `,` is the
|
||||
@ -334,55 +379,121 @@ where
|
||||
/// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an
|
||||
/// error with the appropriate span is emitted to `sess` and a dummy value is returned.
|
||||
fn parse_sep_and_kleene_op<I>(
|
||||
input: &mut I,
|
||||
input: &mut Peekable<I>,
|
||||
span: Span,
|
||||
sess: &ParseSess,
|
||||
features: &RefCell<Features>,
|
||||
attrs: &[ast::Attribute],
|
||||
) -> (Option<token::Token>, KleeneOp)
|
||||
where
|
||||
I: Iterator<Item = tokenstream::TokenTree>,
|
||||
{
|
||||
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
|
||||
match *token {
|
||||
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
|
||||
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
|
||||
_ => None,
|
||||
// We basically look at two token trees here, denoted as #1 and #2 below
|
||||
let span = match parse_kleene_op(input, span) {
|
||||
// #1 is a `+` or `*` KleeneOp
|
||||
//
|
||||
// `?` is ambiguous: it could be a separator or a Kleene::ZeroOrOne, so we need to look
|
||||
// ahead one more token to be sure.
|
||||
Ok(Ok(op)) if op != KleeneOp::ZeroOrOne => return (None, op),
|
||||
|
||||
// #1 is `?` token, but it could be a Kleene::ZeroOrOne without a separator or it could
|
||||
// be a `?` separator followed by any Kleene operator. We need to look ahead 1 token to
|
||||
// find out which.
|
||||
Ok(Ok(op)) => {
|
||||
assert_eq!(op, KleeneOp::ZeroOrOne);
|
||||
|
||||
// Lookahead at #2. If it is a KleenOp, then #1 is a separator.
|
||||
let is_1_sep = if let Some(&tokenstream::TokenTree::Token(_, ref tok2)) = input.peek() {
|
||||
kleene_op(tok2).is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_1_sep {
|
||||
// #1 is a separator and #2 should be a KleepeOp::*
|
||||
// (N.B. We need to advance the input iterator.)
|
||||
match parse_kleene_op(input, span) {
|
||||
// #2 is a KleeneOp (this is the only valid option) :)
|
||||
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
|
||||
if !features.borrow().macro_at_most_once_rep
|
||||
&& !attr::contains_name(attrs, "allow_internal_unstable")
|
||||
{
|
||||
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
|
||||
emit_feature_err(
|
||||
sess,
|
||||
"macro_at_most_once_rep",
|
||||
span,
|
||||
GateIssue::Language,
|
||||
explain,
|
||||
);
|
||||
}
|
||||
return (Some(token::Question), op);
|
||||
}
|
||||
Ok(Ok(op)) => return (Some(token::Question), op),
|
||||
|
||||
// #2 is a random token (this is an error) :(
|
||||
Ok(Err((_, span))) => span,
|
||||
|
||||
// #2 is not even a token at all :(
|
||||
Err(span) => span,
|
||||
}
|
||||
} else {
|
||||
if !features.borrow().macro_at_most_once_rep
|
||||
&& !attr::contains_name(attrs, "allow_internal_unstable")
|
||||
{
|
||||
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
|
||||
emit_feature_err(
|
||||
sess,
|
||||
"macro_at_most_once_rep",
|
||||
span,
|
||||
GateIssue::Language,
|
||||
explain,
|
||||
);
|
||||
}
|
||||
|
||||
// #2 is a random tree and #1 is KleeneOp::ZeroOrOne
|
||||
return (None, op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We attempt to look at the next two token trees in `input`. I will call the first #1 and the
|
||||
// second #2. If #1 and #2 don't match a valid KleeneOp with/without separator, that is an
|
||||
// error, and we should emit an error on the most specific span possible.
|
||||
let span = match input.next() {
|
||||
// #1 is a token
|
||||
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
|
||||
// #1 is a KleeneOp with no separator
|
||||
Some(op) => return (None, op),
|
||||
// #1 is a separator followed by #2, a KleeneOp
|
||||
Ok(Err((tok, span))) => match parse_kleene_op(input, span) {
|
||||
// #2 is a KleeneOp :D
|
||||
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
|
||||
if !features.borrow().macro_at_most_once_rep
|
||||
&& !attr::contains_name(attrs, "allow_internal_unstable")
|
||||
{
|
||||
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
|
||||
emit_feature_err(
|
||||
sess,
|
||||
"macro_at_most_once_rep",
|
||||
span,
|
||||
GateIssue::Language,
|
||||
explain,
|
||||
);
|
||||
}
|
||||
return (Some(tok), op);
|
||||
}
|
||||
Ok(Ok(op)) => return (Some(tok), op),
|
||||
|
||||
// #1 is not a KleeneOp, but may be a separator... need to look at #2
|
||||
None => match input.next() {
|
||||
// #2 is a token
|
||||
Some(tokenstream::TokenTree::Token(span, tok2)) => match kleene_op(&tok2) {
|
||||
// #2 is a KleeneOp, so #1 must be a separator
|
||||
Some(op) => return (Some(tok), op),
|
||||
// #2 is a random token :(
|
||||
Ok(Err((_, span))) => span,
|
||||
|
||||
// #2 is not a KleeneOp... error
|
||||
None => span,
|
||||
},
|
||||
|
||||
// #2 is not a token at all... error
|
||||
tree => tree.as_ref()
|
||||
.map(tokenstream::TokenTree::span)
|
||||
.unwrap_or(span),
|
||||
},
|
||||
// #2 is not a token at all :(
|
||||
Err(span) => span,
|
||||
},
|
||||
|
||||
// #1 is not a token at all... error
|
||||
tree => tree.as_ref()
|
||||
.map(tokenstream::TokenTree::span)
|
||||
.unwrap_or(span),
|
||||
// #1 is not a token
|
||||
Err(span) => span,
|
||||
};
|
||||
|
||||
// Error...
|
||||
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
|
||||
if !features.borrow().macro_at_most_once_rep
|
||||
&& !attr::contains_name(attrs, "allow_internal_unstable")
|
||||
{
|
||||
sess.span_diagnostic
|
||||
.span_err(span, "expected one of: `*`, `+`, or `?`");
|
||||
} else {
|
||||
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
|
||||
}
|
||||
(None, KleeneOp::ZeroOrMore)
|
||||
}
|
||||
|
@ -446,6 +446,9 @@ declare_features! (
|
||||
|
||||
// Allows `#[repr(transparent)]` attribute on newtype structs
|
||||
(active, repr_transparent, "1.25.0", Some(43036)),
|
||||
|
||||
// Use `?` as the Kleene "at most one" operator
|
||||
(active, macro_at_most_once_rep, "1.25.0", Some(48075)),
|
||||
);
|
||||
|
||||
declare_features! (
|
||||
@ -1258,6 +1261,9 @@ pub const EXPLAIN_PLACEMENT_IN: &'static str =
|
||||
pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str =
|
||||
"Unsized tuple coercion is not stable enough for use and is subject to change";
|
||||
|
||||
pub const EXPLAIN_MACRO_AT_MOST_ONCE_REP: &'static str =
|
||||
"Using the `?` macro Kleene operator for \"at most one\" repetition is unstable";
|
||||
|
||||
struct PostExpansionVisitor<'a> {
|
||||
context: &'a Context<'a>,
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
#![allow(unused_macros)]
|
||||
|
||||
macro_rules! assign {
|
||||
(($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected `*` or `+`
|
||||
(($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected one of: `*`, `+`, or `?`
|
||||
$($a)* = $($b)*
|
||||
}
|
||||
}
|
||||
|
53
src/test/compile-fail/macro-at-most-once-rep-ambig.rs
Normal file
53
src/test/compile-fail/macro-at-most-once-rep-ambig.rs
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// The logic for parsing Kleene operators in macros has a special case to disambiguate `?`.
|
||||
// Specifically, `$(pat)?` is the ZeroOrOne operator whereas `$(pat)?+` or `$(pat)?*` are the
|
||||
// ZeroOrMore and OneOrMore operators using `?` as a separator. These tests are intended to
|
||||
// exercise that logic in the macro parser.
|
||||
//
|
||||
// Moreover, we also throw in some tests for using a separator with `?`, which is meaningless but
|
||||
// included for consistency with `+` and `*`.
|
||||
//
|
||||
// This test focuses on error cases.
|
||||
|
||||
#![feature(macro_at_most_once_rep)]
|
||||
|
||||
macro_rules! foo {
|
||||
($(a)?) => {}
|
||||
}
|
||||
|
||||
macro_rules! baz {
|
||||
($(a),?) => {} // comma separator is meaningless for `?`
|
||||
}
|
||||
|
||||
macro_rules! barplus {
|
||||
($(a)?+) => {}
|
||||
}
|
||||
|
||||
macro_rules! barstar {
|
||||
($(a)?*) => {}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
foo!(a?a?a); //~ ERROR no rules expected the token `?`
|
||||
foo!(a?a); //~ ERROR no rules expected the token `?`
|
||||
foo!(a?); //~ ERROR no rules expected the token `?`
|
||||
baz!(a?a?a); //~ ERROR no rules expected the token `?`
|
||||
baz!(a?a); //~ ERROR no rules expected the token `?`
|
||||
baz!(a?); //~ ERROR no rules expected the token `?`
|
||||
baz!(a,); //~ ERROR unexpected end of macro invocation
|
||||
baz!(a?a?a,); //~ ERROR no rules expected the token `?`
|
||||
baz!(a?a,); //~ ERROR no rules expected the token `?`
|
||||
baz!(a?,); //~ ERROR no rules expected the token `?`
|
||||
barplus!(); //~ ERROR unexpected end of macro invocation
|
||||
barplus!(a?); //~ ERROR unexpected end of macro invocation
|
||||
barstar!(a?); //~ ERROR unexpected end of macro invocation
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
macro_rules! foo {
|
||||
{ $+ } => { //~ ERROR expected identifier, found `+`
|
||||
//~^ ERROR missing fragment specifier
|
||||
$(x)(y) //~ ERROR expected `*` or `+`
|
||||
$(x)(y) //~ ERROR expected one of: `*`, `+`, or `?`
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ extern crate syntax_pos;
|
||||
extern crate rustc;
|
||||
extern crate rustc_plugin;
|
||||
|
||||
use syntax::feature_gate::Features;
|
||||
use syntax::parse::token::{NtExpr, NtPat};
|
||||
use syntax::ast::{Ident, Pat};
|
||||
use syntax::tokenstream::{TokenTree};
|
||||
@ -31,11 +32,17 @@ use syntax::ptr::P;
|
||||
use syntax_pos::Span;
|
||||
use rustc_plugin::Registry;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn expand_mbe_matches(cx: &mut ExtCtxt, _: Span, args: &[TokenTree])
|
||||
-> Box<MacResult + 'static> {
|
||||
|
||||
let mbe_matcher = quote_tokens!(cx, $$matched:expr, $$($$pat:pat)|+);
|
||||
let mbe_matcher = quoted::parse(mbe_matcher.into_iter().collect(), true, cx.parse_sess);
|
||||
let mbe_matcher = quoted::parse(mbe_matcher.into_iter().collect(),
|
||||
true,
|
||||
cx.parse_sess,
|
||||
&RefCell::new(Features::new()),
|
||||
&[]);
|
||||
let map = match TokenTree::parse(cx, &mbe_matcher, args.iter().cloned().collect()) {
|
||||
Success(map) => map,
|
||||
Failure(_, tok) => {
|
||||
|
88
src/test/run-pass/macro-at-most-once-rep.rs
Normal file
88
src/test/run-pass/macro-at-most-once-rep.rs
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// The logic for parsing Kleene operators in macros has a special case to disambiguate `?`.
|
||||
// Specifically, `$(pat)?` is the ZeroOrOne operator whereas `$(pat)?+` or `$(pat)?*` are the
|
||||
// ZeroOrMore and OneOrMore operators using `?` as a separator. These tests are intended to
|
||||
// exercise that logic in the macro parser.
|
||||
//
|
||||
// Moreover, we also throw in some tests for using a separator with `?`, which is meaningless but
|
||||
// included for consistency with `+` and `*`.
|
||||
//
|
||||
// This test focuses on non-error cases and making sure the correct number of repetitions happen.
|
||||
|
||||
#![feature(macro_at_most_once_rep)]
|
||||
|
||||
macro_rules! foo {
|
||||
($($a:ident)? ; $num:expr) => { {
|
||||
let mut x = 0;
|
||||
|
||||
$(
|
||||
x += $a;
|
||||
)?
|
||||
|
||||
assert_eq!(x, $num);
|
||||
} }
|
||||
}
|
||||
|
||||
macro_rules! baz {
|
||||
($($a:ident),? ; $num:expr) => { { // comma separator is meaningless for `?`
|
||||
let mut x = 0;
|
||||
|
||||
$(
|
||||
x += $a;
|
||||
)?
|
||||
|
||||
assert_eq!(x, $num);
|
||||
} }
|
||||
}
|
||||
|
||||
macro_rules! barplus {
|
||||
($($a:ident)?+ ; $num:expr) => { {
|
||||
let mut x = 0;
|
||||
|
||||
$(
|
||||
x += $a;
|
||||
)+
|
||||
|
||||
assert_eq!(x, $num);
|
||||
} }
|
||||
}
|
||||
|
||||
macro_rules! barstar {
|
||||
($($a:ident)?* ; $num:expr) => { {
|
||||
let mut x = 0;
|
||||
|
||||
$(
|
||||
x += $a;
|
||||
)*
|
||||
|
||||
assert_eq!(x, $num);
|
||||
} }
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let a = 1;
|
||||
|
||||
// accept 0 or 1 repetitions
|
||||
foo!( ; 0);
|
||||
foo!(a ; 1);
|
||||
baz!( ; 0);
|
||||
baz!(a ; 1);
|
||||
|
||||
// Make sure using ? as a separator works as before
|
||||
barplus!(a ; 1);
|
||||
barplus!(a?a ; 2);
|
||||
barplus!(a?a?a ; 3);
|
||||
barstar!( ; 0);
|
||||
barstar!(a ; 1);
|
||||
barstar!(a?a ; 2);
|
||||
barstar!(a?a?a ; 3);
|
||||
}
|
19
src/test/ui/feature-gate-macro_at_most_once_rep.rs
Normal file
19
src/test/ui/feature-gate-macro_at_most_once_rep.rs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// Test that `?` macro Kleene operator can not be used when the `macro_at_most_once_rep` feature
|
||||
// gate is not used.
|
||||
|
||||
macro_rules! m { ($(a)?) => {} }
|
||||
//~^ ERROR Using the `?` macro Kleene operator for "at most one" repetition is unstable
|
||||
|
||||
fn main() {
|
||||
m!();
|
||||
}
|
10
src/test/ui/feature-gate-macro_at_most_once_rep.stderr
Normal file
10
src/test/ui/feature-gate-macro_at_most_once_rep.stderr
Normal file
@ -0,0 +1,10 @@
|
||||
error[E0658]: Using the `?` macro Kleene operator for "at most one" repetition is unstable (see issue #48075)
|
||||
--> $DIR/feature-gate-macro_at_most_once_rep.rs:14:20
|
||||
|
|
||||
14 | macro_rules! m { ($(a)?) => {} }
|
||||
| ^^^
|
||||
|
|
||||
= help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
Loading…
x
Reference in New Issue
Block a user