about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-07-24 15:11:56 +0000
committerbors <bors@rust-lang.org>2018-07-24 15:11:56 +0000
commitf498e4ec1b57c3245a2fb8e0d5d836ed56760d2d (patch)
tree870ef7d674c7aee50c7d3b59b4dce7053b1589ac /src
parent6a3db033ad05f156281d50ee489d727ee0e5d767 (diff)
parent10ee0f68a6815fafa69f58daf347f0c2a8339f32 (diff)
downloadrust-f498e4ec1b57c3245a2fb8e0d5d836ed56760d2d.tar.gz
rust-f498e4ec1b57c3245a2fb8e0d5d836ed56760d2d.zip
Auto merge of #51587 - mark-i-m:at_most_once_rep_2018, r=alexcrichton
2018 edition `?` Kleene operator

This is my first attempt at implementing the migration lint + 2018 behavior as discussed in #48075

r? @nikomatsakis
Diffstat (limited to 'src')
-rw-r--r--src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md6
-rw-r--r--src/librustc/lint/builtin.rs10
-rw-r--r--src/librustc/lint/mod.rs14
-rw-r--r--src/librustc/macros.rs16
-rw-r--r--src/librustc_driver/driver.rs10
-rw-r--r--src/librustc_lint/builtin.rs4
-rw-r--r--src/librustc_lint/lib.rs15
-rw-r--r--src/libsyntax/early_buffered_lints.rs39
-rw-r--r--src/libsyntax/ext/expand.rs18
-rw-r--r--src/libsyntax/ext/tt/macro_rules.rs25
-rw-r--r--src/libsyntax/ext/tt/quoted.rs290
-rw-r--r--src/libsyntax/lib.rs2
-rw-r--r--src/libsyntax/parse/lexer/mod.rs1
-rw-r--r--src/libsyntax/parse/mod.rs23
-rw-r--r--src/test/parse-fail/issue-33569.rs2
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs11
-rw-r--r--src/test/run-pass/macro-at-most-once-rep.rs49
-rw-r--r--src/test/ui/feature-gate-macro_at_most_once_rep.stderr11
-rw-r--r--src/test/ui/issue-39388.rs2
-rw-r--r--src/test/ui/issue-39388.stderr4
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs28
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.stderr18
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.rs (renamed from src/test/ui/feature-gate-macro_at_most_once_rep.rs)18
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.stderr18
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.rs38
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.stderr24
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.rs45
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.stderr71
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2018.rs53
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-2018.stderr74
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-ambig.rs53
-rw-r--r--src/test/ui/macros/macro-at-most-once-rep-ambig.stderr80
32 files changed, 769 insertions, 303 deletions
diff --git a/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md b/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md
index ec9d85db107..251fc720912 100644
--- a/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md
+++ b/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md
@@ -1,13 +1,15 @@
 # `macro_at_most_once_rep`
 
-The tracking issue for this feature is: TODO(mark-i-m)
+NOTE: This feature is only available in the 2018 Edition.
+
+The tracking issue for this feature is: #48075
 
 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
+```rust,ignore
 #![feature(macro_at_most_once_rep)]
 
 macro_rules! foo {
diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs
index 47c5f464131..4184cba7db3 100644
--- a/src/librustc/lint/builtin.rs
+++ b/src/librustc/lint/builtin.rs
@@ -331,6 +331,15 @@ declare_lint! {
      via the module system"
 }
 
+/// Some lints that are buffered from `libsyntax`. See `syntax::early_buffered_lints`.
+pub mod parser {
+    declare_lint! {
+        pub QUESTION_MARK_MACRO_SEP,
+        Allow,
+        "detects the use of `?` as a macro separator"
+    }
+}
+
 /// Does nothing as a lint pass, but registers some `Lint`s
 /// which are used by other parts of the compiler.
 #[derive(Copy, Clone)]
@@ -389,6 +398,7 @@ impl LintPass for HardwiredLints {
             WHERE_CLAUSES_OBJECT_SAFETY,
             PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
             MACRO_USE_EXTERN_CRATE,
+            parser::QUESTION_MARK_MACRO_SEP,
         )
     }
 }
diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs
index c0f3c351d26..3c1b2056208 100644
--- a/src/librustc/lint/mod.rs
+++ b/src/librustc/lint/mod.rs
@@ -38,10 +38,12 @@ use hir::def_id::{CrateNum, LOCAL_CRATE};
 use hir::intravisit;
 use hir;
 use lint::builtin::BuiltinLintDiagnostics;
+use lint::builtin::parser::QUESTION_MARK_MACRO_SEP;
 use session::{Session, DiagnosticMessageId};
 use std::{hash, ptr};
 use syntax::ast;
 use syntax::codemap::{MultiSpan, ExpnFormat};
+use syntax::early_buffered_lints::BufferedEarlyLintId;
 use syntax::edition::Edition;
 use syntax::symbol::Symbol;
 use syntax::visit as ast_visit;
@@ -86,6 +88,13 @@ pub struct Lint {
 }
 
 impl Lint {
+    /// Returns the `rust::lint::Lint` for a `syntax::early_buffered_lints::BufferedEarlyLintId`.
+    pub fn from_parser_lint_id(lint_id: BufferedEarlyLintId) -> &'static Self {
+        match lint_id {
+            BufferedEarlyLintId::QuestionMarkMacroSep => QUESTION_MARK_MACRO_SEP,
+        }
+    }
+
     /// Get the lint's name, with ASCII letters converted to lowercase.
     pub fn name_lower(&self) -> String {
         self.name.to_ascii_lowercase()
@@ -118,7 +127,7 @@ macro_rules! declare_lint {
         };
     );
     ($vis: vis $NAME: ident, $Level: ident, $desc: expr,
-     $lint_edition: expr => $edition_level: ident $(,)?
+     $lint_edition: expr => $edition_level: ident
     ) => (
         $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
             name: stringify!($NAME),
@@ -133,7 +142,8 @@ macro_rules! declare_lint {
 /// Declare a static `LintArray` and return it as an expression.
 #[macro_export]
 macro_rules! lint_array {
-    ($( $lint:expr ),* $(,)?) => {{
+    ($( $lint:expr ),* ,) => { lint_array!( $($lint),* ) };
+    ($( $lint:expr ),*) => {{
         vec![$($lint),*]
     }}
 }
diff --git a/src/librustc/macros.rs b/src/librustc/macros.rs
index ccd9024f4aa..0bf1f4decc4 100644
--- a/src/librustc/macros.rs
+++ b/src/librustc/macros.rs
@@ -71,7 +71,9 @@ macro_rules! __impl_stable_hash_field {
 
 #[macro_export]
 macro_rules! impl_stable_hash_for {
-    (enum $enum_name:path { $( $variant:ident $( ( $($field:ident $(-> $delegate:tt)?),* ) )* ),* $(,)? }) => {
+    // FIXME(mark-i-m): Some of these should be `?` rather than `*`. See the git blame and change
+    // them back when `?` is supported again.
+    (enum $enum_name:path { $( $variant:ident $( ( $($field:ident $(-> $delegate:tt)*),* ) )* ),* $(,)* }) => {
         impl<'a, 'tcx> ::rustc_data_structures::stable_hasher::HashStable<$crate::ich::StableHashingContext<'a>> for $enum_name {
             #[inline]
             fn hash_stable<W: ::rustc_data_structures::stable_hasher::StableHasherResult>(&self,
@@ -83,14 +85,15 @@ macro_rules! impl_stable_hash_for {
                 match *self {
                     $(
                         $variant $( ( $(ref $field),* ) )* => {
-                            $($( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)?) );*)*
+                            $($( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)*) );*)*
                         }
                     )*
                 }
             }
         }
     };
-    (struct $struct_name:path { $($field:ident $(-> $delegate:tt)?),*  $(,)? }) => {
+    // FIXME(mark-i-m): same here.
+    (struct $struct_name:path { $($field:ident $(-> $delegate:tt)*),*  $(,)* }) => {
         impl<'a, 'tcx> ::rustc_data_structures::stable_hasher::HashStable<$crate::ich::StableHashingContext<'a>> for $struct_name {
             #[inline]
             fn hash_stable<W: ::rustc_data_structures::stable_hasher::StableHasherResult>(&self,
@@ -100,11 +103,12 @@ macro_rules! impl_stable_hash_for {
                     $(ref $field),*
                 } = *self;
 
-                $( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)?) );*
+                $( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)*) );*
             }
         }
     };
-    (tuple_struct $struct_name:path { $($field:ident $(-> $delegate:tt)?),*  $(,)? }) => {
+    // FIXME(mark-i-m): same here.
+    (tuple_struct $struct_name:path { $($field:ident $(-> $delegate:tt)*),*  $(,)* }) => {
         impl<'a, 'tcx> ::rustc_data_structures::stable_hasher::HashStable<$crate::ich::StableHashingContext<'a>> for $struct_name {
             #[inline]
             fn hash_stable<W: ::rustc_data_structures::stable_hasher::StableHasherResult>(&self,
@@ -114,7 +118,7 @@ macro_rules! impl_stable_hash_for {
                     $(ref $field),*
                 ) = *self;
 
-                $( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)?) );*
+                $( __impl_stable_hash_field!($field, __ctx, __hasher $(, $delegate)*) );*
             }
         }
     };
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index c016a131507..91392ab013d 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -52,6 +52,7 @@ use std::path::{Path, PathBuf};
 use rustc_data_structures::sync::{self, Lrc, Lock};
 use std::sync::mpsc;
 use syntax::{self, ast, attr, diagnostics, visit};
+use syntax::early_buffered_lints::BufferedEarlyLint;
 use syntax::ext::base::ExtCtxt;
 use syntax::fold::Folder;
 use syntax::parse::{self, PResult};
@@ -1066,6 +1067,15 @@ where
         )
     });
 
+    // Add all buffered lints from the `ParseSess` to the `Session`.
+    sess.parse_sess.buffered_lints.with_lock(|buffered_lints| {
+        info!("{} parse sess buffered_lints", buffered_lints.len());
+        for BufferedEarlyLint{id, span, msg, lint_id} in buffered_lints.drain(..) {
+            let lint = lint::Lint::from_parser_lint_id(lint_id);
+            sess.buffer_lint(lint, id, span, &msg);
+        }
+    });
+
     // Done with macro expansion!
 
     after_expand(&krate)?;
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 3ffbdc7b7dc..e6aa7c0d16c 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -617,7 +617,7 @@ declare_lint! {
     pub ANONYMOUS_PARAMETERS,
     Allow,
     "detects anonymous parameters",
-    Edition::Edition2018 => Warn,
+    Edition::Edition2018 => Warn
 }
 
 /// Checks for use of anonymous parameters (RFC 1685)
@@ -1706,7 +1706,7 @@ impl LintPass for SoftLints {
             UNIONS_WITH_DROP_FIELDS,
             UNREACHABLE_PUB,
             TYPE_ALIAS_BOUNDS,
-            TRIVIAL_BOUNDS,
+            TRIVIAL_BOUNDS
         )
     }
 }
diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs
index cab51fbd987..798c289ac2f 100644
--- a/src/librustc_lint/lib.rs
+++ b/src/librustc_lint/lib.rs
@@ -44,9 +44,13 @@ extern crate syntax_pos;
 
 use rustc::lint;
 use rustc::lint::{LateContext, LateLintPass, LintPass, LintArray};
-use rustc::lint::builtin::{BARE_TRAIT_OBJECTS, ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
-                           ELIDED_LIFETIMES_IN_PATHS};
-use rustc::lint::builtin::MACRO_USE_EXTERN_CRATE;
+use rustc::lint::builtin::{
+    BARE_TRAIT_OBJECTS,
+    ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
+    MACRO_USE_EXTERN_CRATE,
+    ELIDED_LIFETIMES_IN_PATHS,
+    parser::QUESTION_MARK_MACRO_SEP
+};
 use rustc::session;
 use rustc::util;
 use rustc::hir;
@@ -321,6 +325,11 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
             reference: "issue #50504 <https://github.com/rust-lang/rust/issues/50504>",
             edition: None,
         },
+        FutureIncompatibleInfo {
+            id: LintId::of(QUESTION_MARK_MACRO_SEP),
+            reference: "issue #48075 <https://github.com/rust-lang/rust/issues/48075>",
+            edition: Some(Edition::Edition2018),
+        }
         ]);
 
     // Register renamed and removed lints
diff --git a/src/libsyntax/early_buffered_lints.rs b/src/libsyntax/early_buffered_lints.rs
new file mode 100644
index 00000000000..a976af1435d
--- /dev/null
+++ b/src/libsyntax/early_buffered_lints.rs
@@ -0,0 +1,39 @@
+// 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.
+
+//! Allows the buffering of lints for later.
+//!
+//! Since we cannot have a dependency on `librustc`, we implement some types here that are somewhat
+//! redundant. Later, these types can be converted to types for use by the rest of the compiler.
+
+use syntax::ast::NodeId;
+use syntax_pos::MultiSpan;
+
+/// Since we cannot import `LintId`s from `rustc::lint`, we define some Ids here which can later be
+/// passed to `rustc::lint::Lint::from_parser_lint_id` to get a `rustc::lint::Lint`.
+pub enum BufferedEarlyLintId {
+    /// Usage of `?` as a macro separator is deprecated.
+    QuestionMarkMacroSep,
+}
+
+/// Stores buffered lint info which can later be passed to `librustc`.
+pub struct BufferedEarlyLint {
+    /// The span of code that we are linting on.
+   pub span: MultiSpan,
+
+   /// The lint message.
+   pub msg: String,
+
+   /// The `NodeId` of the AST node that generated the lint.
+   pub id: NodeId,
+
+   /// A lint Id that can be passed to `rustc::lint::Lint::from_parser_lint_id`.
+   pub lint_id: BufferedEarlyLintId,
+}
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index dc461d0a15d..b84046d1050 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -44,8 +44,10 @@ macro_rules! ast_fragments {
     (
         $($Kind:ident($AstTy:ty) {
             $kind_name:expr;
-            $(one fn $fold_ast:ident; fn $visit_ast:ident;)?
-            $(many fn $fold_ast_elt:ident; fn $visit_ast_elt:ident;)?
+            // FIXME: HACK: this should be `$(one ...)?` and `$(many ...)?` but `?` macro
+            // repetition was removed from 2015 edition in #51587 because of ambiguities.
+            $(one fn $fold_ast:ident; fn $visit_ast:ident;)*
+            $(many fn $fold_ast_elt:ident; fn $visit_ast_elt:ident;)*
             fn $make_ast:ident;
         })*
     ) => {
@@ -100,11 +102,11 @@ macro_rules! ast_fragments {
                     AstFragment::OptExpr(expr) =>
                         AstFragment::OptExpr(expr.and_then(|expr| folder.fold_opt_expr(expr))),
                     $($(AstFragment::$Kind(ast) =>
-                        AstFragment::$Kind(folder.$fold_ast(ast)),)?)*
+                        AstFragment::$Kind(folder.$fold_ast(ast)),)*)*
                     $($(AstFragment::$Kind(ast) =>
                         AstFragment::$Kind(ast.into_iter()
                                               .flat_map(|ast| folder.$fold_ast_elt(ast))
-                                              .collect()),)?)*
+                                              .collect()),)*)*
                 }
             }
 
@@ -112,10 +114,10 @@ macro_rules! ast_fragments {
                 match *self {
                     AstFragment::OptExpr(Some(ref expr)) => visitor.visit_expr(expr),
                     AstFragment::OptExpr(None) => {}
-                    $($(AstFragment::$Kind(ref ast) => visitor.$visit_ast(ast),)?)*
+                    $($(AstFragment::$Kind(ref ast) => visitor.$visit_ast(ast),)*)*
                     $($(AstFragment::$Kind(ref ast) => for ast_elt in &ast[..] {
                         visitor.$visit_ast_elt(ast_elt);
-                    })?)*
+                    })*)*
                 }
             }
         }
@@ -126,10 +128,10 @@ macro_rules! ast_fragments {
             }
             $($(fn $fold_ast(&mut self, ast: $AstTy) -> $AstTy {
                 self.expand_fragment(AstFragment::$Kind(ast)).$make_ast()
-            })?)*
+            })*)*
             $($(fn $fold_ast_elt(&mut self, ast_elt: <$AstTy as IntoIterator>::Item) -> $AstTy {
                 self.expand_fragment(AstFragment::$Kind(SmallVector::one(ast_elt))).$make_ast()
-            })?)*
+            })*)*
         }
 
         impl<'a> MacResult for ::ext::tt::macro_rules::ParserAnyMacro<'a> {
diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs
index 9ebead1062e..c9ec2c7d1e8 100644
--- a/src/libsyntax/ext/tt/macro_rules.rs
+++ b/src/libsyntax/ext/tt/macro_rules.rs
@@ -240,8 +240,17 @@ pub fn compile(sess: &ParseSess, features: &Features, def: &ast::Item, edition:
             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, features, &def.attrs)
-                            .pop().unwrap();
+                        let tt = quoted::parse(
+                            tt.clone().into(),
+                            true,
+                            sess,
+                            features,
+                            &def.attrs,
+                            edition,
+                            def.id,
+                        )
+                        .pop()
+                        .unwrap();
                         valid &= check_lhs_nt_follows(sess, features, &def.attrs, &tt);
                         return tt;
                     }
@@ -257,8 +266,16 @@ pub fn compile(sess: &ParseSess, features: &Features, def: &ast::Item, edition:
             s.iter().map(|m| {
                 if let MatchedNonterminal(ref nt) = *m {
                     if let NtTT(ref tt) = **nt {
-                        return quoted::parse(tt.clone().into(), false, sess, features, &def.attrs)
-                            .pop().unwrap();
+                        return quoted::parse(
+                            tt.clone().into(),
+                            false,
+                            sess,
+                            features,
+                            &def.attrs,
+                            edition,
+                            def.id,
+                        ).pop()
+                         .unwrap();
                     }
                 }
                 sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")
diff --git a/src/libsyntax/ext/tt/quoted.rs b/src/libsyntax/ext/tt/quoted.rs
index d21ffabb62e..357fc77a3a7 100644
--- a/src/libsyntax/ext/tt/quoted.rs
+++ b/src/libsyntax/ext/tt/quoted.rs
@@ -8,17 +8,19 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use {ast, attr};
+use ast::NodeId;
+use early_buffered_lints::BufferedEarlyLintId;
 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};
+use syntax_pos::{edition::Edition, BytePos, Span};
 use tokenstream;
+use {ast, attr};
 
-use std::iter::Peekable;
 use rustc_data_structures::sync::Lrc;
+use std::iter::Peekable;
 
 /// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note
 /// that the delimiter itself might be `NoDelim`.
@@ -174,6 +176,8 @@ impl TokenTree {
 /// - `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.
+/// - `edition`: which edition are we in.
+/// - `macro_node_id`: the NodeId of the macro we are parsing.
 ///
 /// # Returns
 ///
@@ -184,6 +188,8 @@ pub fn parse(
     sess: &ParseSess,
     features: &Features,
     attrs: &[ast::Attribute],
+    edition: Edition,
+    macro_node_id: NodeId,
 ) -> Vec<TokenTree> {
     // Will contain the final collection of `self::TokenTree`
     let mut result = Vec::new();
@@ -194,7 +200,16 @@ pub fn parse(
     while let Some(tree) = trees.next() {
         // 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);
+        let tree = parse_tree(
+            tree,
+            &mut trees,
+            expect_matchers,
+            sess,
+            features,
+            attrs,
+            edition,
+            macro_node_id,
+        );
         match tree {
             TokenTree::MetaVar(start_sp, ident) if expect_matchers => {
                 let span = match trees.next() {
@@ -207,11 +222,13 @@ pub fn parse(
                             }
                             _ => end_sp,
                         },
-                        tree => tree.as_ref()
+                        tree => tree
+                            .as_ref()
                             .map(tokenstream::TokenTree::span)
                             .unwrap_or(span),
                     },
-                    tree => tree.as_ref()
+                    tree => tree
+                        .as_ref()
                         .map(tokenstream::TokenTree::span)
                         .unwrap_or(start_sp),
                 };
@@ -252,6 +269,8 @@ fn parse_tree<I>(
     sess: &ParseSess,
     features: &Features,
     attrs: &[ast::Attribute],
+    edition: Edition,
+    macro_node_id: NodeId,
 ) -> TokenTree
 where
     I: Iterator<Item = tokenstream::TokenTree>,
@@ -270,9 +289,26 @@ where
                     sess.span_diagnostic.span_err(span, &msg);
                 }
                 // Parse the contents of the sequence itself
-                let sequence = parse(delimited.tts.into(), expect_matchers, sess, features, attrs);
+                let sequence = parse(
+                    delimited.tts.into(),
+                    expect_matchers,
+                    sess,
+                    features,
+                    attrs,
+                    edition,
+                    macro_node_id,
+                );
                 // Get the Kleene operator and optional separator
-                let (separator, op) = parse_sep_and_kleene_op(trees, span, sess, features, attrs);
+                let (separator, op) =
+                    parse_sep_and_kleene_op(
+                        trees,
+                        span,
+                        sess,
+                        features,
+                        attrs,
+                        edition,
+                        macro_node_id,
+                    );
                 // Count the number of captured "names" (i.e. named metavars)
                 let name_captures = macro_parser::count_names(&sequence);
                 TokenTree::Sequence(
@@ -322,7 +358,15 @@ where
             span,
             Lrc::new(Delimited {
                 delim: delimited.delim,
-                tts: parse(delimited.tts.into(), expect_matchers, sess, features, attrs),
+                tts: parse(
+                    delimited.tts.into(),
+                    expect_matchers,
+                    sess,
+                    features,
+                    attrs,
+                    edition,
+                    macro_node_id,
+                ),
             }),
         ),
     }
@@ -341,22 +385,23 @@ fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
 
 /// 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(Ok((op, span))) 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>
+) -> Result<Result<(KleeneOp, Span), (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)),
+            Some(op) => Ok(Ok((op, span))),
             None => Ok(Err((tok, span))),
         },
-        tree => Err(tree.as_ref()
+        tree => Err(tree
+            .as_ref()
             .map(tokenstream::TokenTree::span)
             .unwrap_or(span)),
     }
@@ -374,12 +419,43 @@ where
 /// session `sess`. If the next one (or possibly two) tokens in `input` correspond to a Kleene
 /// 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.
+///
+/// NOTE: In 2015 edition, * and + are the only Kleene operators and `?` is a separator. In 2018,
+/// `?` is a Kleene op and not a separator.
 fn parse_sep_and_kleene_op<I>(
     input: &mut Peekable<I>,
     span: Span,
     sess: &ParseSess,
     features: &Features,
     attrs: &[ast::Attribute],
+    edition: Edition,
+    macro_node_id: NodeId,
+) -> (Option<token::Token>, KleeneOp)
+where
+    I: Iterator<Item = tokenstream::TokenTree>,
+{
+    match edition {
+        Edition::Edition2015 => parse_sep_and_kleene_op_2015(
+            input,
+            span,
+            sess,
+            features,
+            attrs,
+            macro_node_id,
+        ),
+        Edition::Edition2018 => parse_sep_and_kleene_op_2018(input, span, sess, features, attrs),
+        _ => unimplemented!(),
+    }
+}
+
+// `?` is a separator (with a migration warning) and never a KleeneOp.
+fn parse_sep_and_kleene_op_2015<I>(
+    input: &mut Peekable<I>,
+    span: Span,
+    sess: &ParseSess,
+    _features: &Features,
+    _attrs: &[ast::Attribute],
+    macro_node_id: NodeId,
 ) -> (Option<token::Token>, KleeneOp)
 where
     I: Iterator<Item = tokenstream::TokenTree>,
@@ -388,14 +464,14 @@ where
     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)) => {
+        // `?` is ambiguous: it could be a separator (warning) or a Kleene::ZeroOrOne (error), 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 (error in 2015) 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, op1_span))) => {
             assert_eq!(op, KleeneOp::ZeroOrOne);
 
             // Lookahead at #2. If it is a KleenOp, then #1 is a separator.
@@ -406,71 +482,149 @@ where
             };
 
             if is_1_sep {
-                // #1 is a separator and #2 should be a KleepeOp::*
+                // #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.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 `?`, which is not allowed as a Kleene op in 2015 edition.
+                    Ok(Ok((op, op2_span))) if op == KleeneOp::ZeroOrOne => {
+                        sess.span_diagnostic
+                            .struct_span_err(op2_span, "expected `*` or `+`")
+                            .note("`?` is not a macro repetition operator")
+                            .emit();
+
+                        // Return a dummy
+                        return (None, KleeneOp::ZeroOrMore);
+                    }
+
+                    // #2 is a Kleene op, which is the the only valid option
+                    Ok(Ok((op, _))) => {
+                        // Warn that `?` as a separator will be deprecated
+                        sess.buffer_lint(
+                            BufferedEarlyLintId::QuestionMarkMacroSep,
+                            op1_span,
+                            macro_node_id,
+                            "using `?` as a separator is deprecated and will be \
+                             a hard error in an upcoming edition",
+                        );
+
                         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,
+                    Ok(Err((_, _))) => op1_span,
 
                     // #2 is not even a token at all :(
-                    Err(span) => span,
+                    Err(_) => op1_span,
                 }
             } else {
-                if !features.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,
-                    );
-                }
+                // `?` is not allowed as a Kleene op in 2015
+                sess.span_diagnostic
+                    .struct_span_err(op1_span, "expected `*` or `+`")
+                    .note("`?` is not a macro repetition operator")
+                    .emit();
+
+                // Return a dummy
+                return (None, KleeneOp::ZeroOrMore);
+            }
+        }
+
+        // #1 is a separator followed by #2, a KleeneOp
+        Ok(Err((tok, span))) => match parse_kleene_op(input, span) {
+            // #2 is a `?`, which is not allowed as a Kleene op in 2015 edition.
+            Ok(Ok((op, op2_span))) if op == KleeneOp::ZeroOrOne => {
+                sess.span_diagnostic
+                    .struct_span_err(op2_span, "expected `*` or `+`")
+                    .note("`?` is not a macro repetition operator")
+                    .emit();
+
+                // Return a dummy
+                return (None, KleeneOp::ZeroOrMore);
+            }
+
+            // #2 is a KleeneOp :D
+            Ok(Ok((op, _))) => return (Some(tok), op),
+
+            // #2 is a random token :(
+            Ok(Err((_, span))) => span,
 
-                // #2 is a random tree and #1 is KleeneOp::ZeroOrOne
+            // #2 is not a token at all :(
+            Err(span) => span,
+        },
+
+        // #1 is not a token
+        Err(span) => span,
+    };
+
+    sess.span_diagnostic.span_err(span, "expected `*` or `+`");
+
+    // Return a dummy
+    (None, KleeneOp::ZeroOrMore)
+}
+
+// `?` is a Kleene op, not a separator
+fn parse_sep_and_kleene_op_2018<I>(
+    input: &mut Peekable<I>,
+    span: Span,
+    sess: &ParseSess,
+    features: &Features,
+    attrs: &[ast::Attribute],
+) -> (Option<token::Token>, KleeneOp)
+where
+    I: Iterator<Item = tokenstream::TokenTree>,
+{
+    // 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 `?` (needs feature gate)
+        Ok(Ok((op, op1_span))) if op == KleeneOp::ZeroOrOne => {
+            if !features.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",
+                    op1_span,
+                    GateIssue::Language,
+                    explain,
+                );
+
+                op1_span
+            } else {
                 return (None, op);
             }
         }
 
+        // #1 is a `+` or `*` KleeneOp
+        Ok(Ok((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 => {
+            // #2 is the `?` Kleene op, which does not take a separator (error)
+            Ok(Ok((op, op2_span))) if op == KleeneOp::ZeroOrOne => {
+                // Error!
+
                 if !features.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",
+                    // FIXME: when `?` as a Kleene op is stabilized, we only need the "does not
+                    // take a macro separator" error (i.e. the `else` case).
+                    sess.span_diagnostic
+                        .struct_span_err(op2_span, "expected `*` or `+`")
+                        .note("`?` is not a macro repetition operator")
+                        .emit();
+                } else {
+                    sess.span_diagnostic.span_err(
                         span,
-                        GateIssue::Language,
-                        explain,
+                        "the `?` macro repetition operator does not take a separator",
                     );
                 }
-                return (Some(tok), op);
+
+                // Return a dummy
+                return (None, KleeneOp::ZeroOrMore);
             }
-            Ok(Ok(op)) => return (Some(tok), op),
+
+            // #2 is a KleeneOp :D
+            Ok(Ok((op, _))) => return (Some(tok), op),
 
             // #2 is a random token :(
             Ok(Err((_, span))) => span,
@@ -483,13 +637,15 @@ where
         Err(span) => span,
     };
 
-    if !features.macro_at_most_once_rep
-        && !attr::contains_name(attrs, "allow_internal_unstable")
-    {
+    // If we ever get to this point, we have experienced an "unexpected token" error
+
+    if !features.macro_at_most_once_rep && !attr::contains_name(attrs, "allow_internal_unstable") {
+        sess.span_diagnostic.span_err(span, "expected `*` or `+`");
+    } else {
         sess.span_diagnostic
             .span_err(span, "expected one of: `*`, `+`, or `?`");
-    } else {
-        sess.span_diagnostic.span_err(span, "expected `*` or `+`");
     }
+
+    // Return a dummy
     (None, KleeneOp::ZeroOrMore)
 }
diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs
index ffaad9bf94c..d241ae1d442 100644
--- a/src/libsyntax/lib.rs
+++ b/src/libsyntax/lib.rs
@@ -181,6 +181,8 @@ pub mod ext {
     }
 }
 
+pub mod early_buffered_lints;
+
 #[cfg(test)]
 mod test_snippet;
 
diff --git a/src/libsyntax/parse/lexer/mod.rs b/src/libsyntax/parse/lexer/mod.rs
index 9748e2947ee..4b077aa8dd4 100644
--- a/src/libsyntax/parse/lexer/mod.rs
+++ b/src/libsyntax/parse/lexer/mod.rs
@@ -1807,6 +1807,7 @@ mod tests {
             raw_identifier_spans: Lock::new(Vec::new()),
             registered_diagnostics: Lock::new(ErrorMap::new()),
             non_modrs_mods: Lock::new(vec![]),
+            buffered_lints: Lock::new(vec![]),
         }
     }
 
diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs
index 1754e5f1b9a..d029509f0c1 100644
--- a/src/libsyntax/parse/mod.rs
+++ b/src/libsyntax/parse/mod.rs
@@ -11,9 +11,10 @@
 //! The main parser interface
 
 use rustc_data_structures::sync::{Lrc, Lock};
-use ast::{self, CrateConfig};
+use ast::{self, CrateConfig, NodeId};
+use early_buffered_lints::{BufferedEarlyLint, BufferedEarlyLintId};
 use codemap::{CodeMap, FilePathMapping};
-use syntax_pos::{Span, FileMap, FileName};
+use syntax_pos::{Span, FileMap, FileName, MultiSpan};
 use errors::{Handler, ColorConfig, DiagnosticBuilder};
 use feature_gate::UnstableFeatures;
 use parse::parser::Parser;
@@ -57,6 +58,7 @@ pub struct ParseSess {
     /// Used to determine and report recursive mod inclusions
     included_mod_stack: Lock<Vec<PathBuf>>,
     code_map: Lrc<CodeMap>,
+    pub buffered_lints: Lock<Vec<BufferedEarlyLint>>,
 }
 
 impl ParseSess {
@@ -80,12 +82,29 @@ impl ParseSess {
             included_mod_stack: Lock::new(vec![]),
             code_map,
             non_modrs_mods: Lock::new(vec![]),
+            buffered_lints: Lock::new(vec![]),
         }
     }
 
     pub fn codemap(&self) -> &CodeMap {
         &self.code_map
     }
+
+    pub fn buffer_lint<S: Into<MultiSpan>>(&self,
+        lint_id: BufferedEarlyLintId,
+        span: S,
+        id: NodeId,
+        msg: &str,
+    ) {
+        self.buffered_lints.with_lock(|buffered_lints| {
+            buffered_lints.push(BufferedEarlyLint{
+                span: span.into(),
+                id,
+                msg: msg.into(),
+                lint_id,
+            });
+        });
+    }
 }
 
 #[derive(Clone)]
diff --git a/src/test/parse-fail/issue-33569.rs b/src/test/parse-fail/issue-33569.rs
index af90d0a83c9..15d491719a6 100644
--- a/src/test/parse-fail/issue-33569.rs
+++ b/src/test/parse-fail/issue-33569.rs
@@ -13,7 +13,7 @@
 macro_rules! foo {
     { $+ } => { //~ ERROR expected identifier, found `+`
                 //~^ ERROR missing fragment specifier
-        $(x)(y) //~ ERROR expected one of: `*`, `+`, or `?`
+        $(x)(y) //~ ERROR expected `*` or `+`
     }
 }
 
diff --git a/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs b/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs
index fd8f7b9e384..f1777745e06 100644
--- a/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs
@@ -20,7 +20,7 @@ extern crate rustc_plugin;
 
 use syntax::feature_gate::Features;
 use syntax::parse::token::{NtExpr, NtPat};
-use syntax::ast::{Ident, Pat};
+use syntax::ast::{Ident, Pat, NodeId};
 use syntax::tokenstream::{TokenTree};
 use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
 use syntax::ext::build::AstBuilder;
@@ -29,11 +29,9 @@ use syntax::ext::tt::macro_parser::{MatchedSeq, MatchedNonterminal};
 use syntax::ext::tt::macro_parser::{Success, Failure, Error};
 use syntax::ext::tt::macro_parser::parse_failure_msg;
 use syntax::ptr::P;
-use syntax_pos::Span;
+use syntax_pos::{Span, edition::Edition};
 use rustc_plugin::Registry;
 
-use std::cell::RefCell;
-
 fn expand_mbe_matches(cx: &mut ExtCtxt, _: Span, args: &[TokenTree])
         -> Box<MacResult + 'static> {
 
@@ -42,7 +40,10 @@ fn expand_mbe_matches(cx: &mut ExtCtxt, _: Span, args: &[TokenTree])
                                     true,
                                     cx.parse_sess,
                                     &Features::new(),
-                                    &[]);
+                                    &[],
+                                    Edition::Edition2015,
+                                    // not used...
+                                    NodeId::new(0));
     let map = match TokenTree::parse(cx, &mbe_matcher, args.iter().cloned().collect()) {
         Success(map) => map,
         Failure(_, tok) => {
diff --git a/src/test/run-pass/macro-at-most-once-rep.rs b/src/test/run-pass/macro-at-most-once-rep.rs
index b7e942f9383..dcf2222ba6d 100644
--- a/src/test/run-pass/macro-at-most-once-rep.rs
+++ b/src/test/run-pass/macro-at-most-once-rep.rs
@@ -18,6 +18,8 @@
 //
 // This test focuses on non-error cases and making sure the correct number of repetitions happen.
 
+// compile-flags: --edition=2018
+
 #![feature(macro_at_most_once_rep)]
 
 macro_rules! foo {
@@ -32,57 +34,10 @@ macro_rules! foo {
     } }
 }
 
-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);
 }
diff --git a/src/test/ui/feature-gate-macro_at_most_once_rep.stderr b/src/test/ui/feature-gate-macro_at_most_once_rep.stderr
deleted file mode 100644
index 9ca71d937f8..00000000000
--- a/src/test/ui/feature-gate-macro_at_most_once_rep.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-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
-   |
-LL | macro_rules! m { ($(a)?) => {} }
-   |                    ^^^
-   |
-   = help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/issue-39388.rs b/src/test/ui/issue-39388.rs
index 6da04937408..15eef429eab 100644
--- a/src/test/ui/issue-39388.rs
+++ b/src/test/ui/issue-39388.rs
@@ -11,7 +11,7 @@
 #![allow(unused_macros)]
 
 macro_rules! assign {
-    (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected one of: `*`, `+`, or `?`
+    (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected `*` or `+`
         $($a)* = $($b)*
     }
 }
diff --git a/src/test/ui/issue-39388.stderr b/src/test/ui/issue-39388.stderr
index a38d38a51ad..dc19487f3af 100644
--- a/src/test/ui/issue-39388.stderr
+++ b/src/test/ui/issue-39388.stderr
@@ -1,7 +1,7 @@
-error: expected one of: `*`, `+`, or `?`
+error: expected `*` or `+`
   --> $DIR/issue-39388.rs:14:22
    |
-LL |     (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected one of: `*`, `+`, or `?`
+LL |     (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected `*` or `+`
    |                      ^^^^^^^
 
 error: aborting due to previous error
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs
new file mode 100644
index 00000000000..fd7925ea3ee
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs
@@ -0,0 +1,28 @@
+// 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.
+
+// Test behavior of `?` macro _kleene op_ under the 2015 edition. Namely, it doesn't exist, even
+// with the feature flag.
+
+// gate-test-macro_at_most_once_rep
+// compile-flags: --edition=2015
+
+#![feature(macro_at_most_once_rep)]
+
+macro_rules! bar {
+    ($(a)?) => {} //~ERROR expected `*` or `+`
+}
+
+macro_rules! baz {
+    ($(a),?) => {} //~ERROR expected `*` or `+`
+}
+
+fn main() {}
+
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.stderr b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.stderr
new file mode 100644
index 00000000000..5f687900421
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep-feature-flag.stderr
@@ -0,0 +1,18 @@
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs:20:10
+   |
+LL |     ($(a)?) => {} //~ERROR expected `*` or `+`
+   |          ^
+   |
+   = note: `?` is not a macro repetition operator
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2015-ques-rep-feature-flag.rs:24:11
+   |
+LL |     ($(a),?) => {} //~ERROR expected `*` or `+`
+   |           ^
+   |
+   = note: `?` is not a macro repetition operator
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/feature-gate-macro_at_most_once_rep.rs b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.rs
index bdce1952a9a..90bc19739b8 100644
--- a/src/test/ui/feature-gate-macro_at_most_once_rep.rs
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.rs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// 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.
 //
@@ -8,12 +8,16 @@
 // 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.
+// Test behavior of `?` macro _kleene op_ under the 2015 edition. Namely, it doesn't exist.
 
-macro_rules! m { ($(a)?) => {} }
-//~^ ERROR using the `?` macro Kleene operator for "at most one" repetition is unstable
+// compile-flags: --edition=2015
 
-fn main() {
-    m!();
+macro_rules! bar {
+    ($(a)?) => {} //~ERROR expected `*` or `+`
 }
+
+macro_rules! baz {
+    ($(a),?) => {} //~ERROR expected `*` or `+`
+}
+
+fn main() {}
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.stderr b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.stderr
new file mode 100644
index 00000000000..8681b5d5be5
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-rep.stderr
@@ -0,0 +1,18 @@
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2015-ques-rep.rs:16:10
+   |
+LL |     ($(a)?) => {} //~ERROR expected `*` or `+`
+   |          ^
+   |
+   = note: `?` is not a macro repetition operator
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2015-ques-rep.rs:20:11
+   |
+LL |     ($(a),?) => {} //~ERROR expected `*` or `+`
+   |           ^
+   |
+   = note: `?` is not a macro repetition operator
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.rs b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.rs
new file mode 100644
index 00000000000..2e06b4bd5c2
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.rs
@@ -0,0 +1,38 @@
+// 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.
+
+// Test behavior of `?` macro _separator_ under the 2015 edition. Namely, `?` can be used as a
+// separator, but you get a migration warning for the edition.
+
+// compile-flags: --edition=2015
+// compile-pass
+
+#![warn(rust_2018_compatibility)]
+
+macro_rules! bar {
+    ($(a)?*) => {} //~WARN using `?` as a separator
+    //~^WARN this was previously accepted
+}
+
+macro_rules! baz {
+    ($(a)?+) => {} //~WARN using `?` as a separator
+    //~^WARN this was previously accepted
+}
+
+fn main() {
+    bar!();
+    bar!(a);
+    bar!(a?a);
+    bar!(a?a?a?a?a);
+
+    baz!(a);
+    baz!(a?a);
+    baz!(a?a?a?a?a);
+}
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.stderr b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.stderr
new file mode 100644
index 00000000000..db1872c2413
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2015-ques-sep.stderr
@@ -0,0 +1,24 @@
+warning: using `?` as a separator is deprecated and will be a hard error in an upcoming edition
+  --> $DIR/macro-at-most-once-rep-2015-ques-sep.rs:20:10
+   |
+LL |     ($(a)?*) => {} //~WARN using `?` as a separator
+   |          ^
+   |
+note: lint level defined here
+  --> $DIR/macro-at-most-once-rep-2015-ques-sep.rs:17:9
+   |
+LL | #![warn(rust_2018_compatibility)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
+   = note: #[warn(question_mark_macro_sep)] implied by #[warn(rust_2018_compatibility)]
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!
+   = note: for more information, see issue #48075 <https://github.com/rust-lang/rust/issues/48075>
+
+warning: using `?` as a separator is deprecated and will be a hard error in an upcoming edition
+  --> $DIR/macro-at-most-once-rep-2015-ques-sep.rs:25:10
+   |
+LL |     ($(a)?+) => {} //~WARN using `?` as a separator
+   |          ^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!
+   = note: for more information, see issue #48075 <https://github.com/rust-lang/rust/issues/48075>
+
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.rs b/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.rs
new file mode 100644
index 00000000000..f3107d4f1e4
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.rs
@@ -0,0 +1,45 @@
+// 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.
+
+// Feature gate test for macro_at_most_once_rep under 2018 edition.
+
+// gate-test-macro_at_most_once_rep
+// compile-flags: --edition=2018
+
+macro_rules! foo {
+    ($(a)?) => {}
+    //~^ERROR using the `?` macro Kleene operator for
+    //~|ERROR expected `*` or `+`
+}
+
+macro_rules! baz {
+    ($(a),?) => {} //~ERROR expected `*` or `+`
+}
+
+macro_rules! barplus {
+    ($(a)?+) => {}
+    //~^ERROR using the `?` macro Kleene operator for
+    //~|ERROR expected `*` or `+`
+}
+
+macro_rules! barstar {
+    ($(a)?*) => {}
+    //~^ERROR using the `?` macro Kleene operator for
+    //~|ERROR expected `*` or `+`
+}
+
+pub fn main() {
+    foo!();
+    foo!(a);
+    foo!(a?); //~ ERROR no rules expected the token `?`
+    foo!(a?a); //~ ERROR no rules expected the token `?`
+    foo!(a?a?a); //~ ERROR no rules expected the token `?`
+}
+
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.stderr b/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.stderr
new file mode 100644
index 00000000000..22f1c94fced
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2018-feature-gate.stderr
@@ -0,0 +1,71 @@
+error[E0658]: using the `?` macro Kleene operator for "at most one" repetition is unstable (see issue #48075)
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:17:10
+   |
+LL |     ($(a)?) => {}
+   |          ^
+   |
+   = help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:17:10
+   |
+LL |     ($(a)?) => {}
+   |          ^
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:23:11
+   |
+LL |     ($(a),?) => {} //~ERROR expected `*` or `+`
+   |           ^
+   |
+   = note: `?` is not a macro repetition operator
+
+error[E0658]: using the `?` macro Kleene operator for "at most one" repetition is unstable (see issue #48075)
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:27:10
+   |
+LL |     ($(a)?+) => {}
+   |          ^
+   |
+   = help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:27:10
+   |
+LL |     ($(a)?+) => {}
+   |          ^
+
+error[E0658]: using the `?` macro Kleene operator for "at most one" repetition is unstable (see issue #48075)
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:33:10
+   |
+LL |     ($(a)?*) => {}
+   |          ^
+   |
+   = help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable
+
+error: expected `*` or `+`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:33:10
+   |
+LL |     ($(a)?*) => {}
+   |          ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:41:11
+   |
+LL |     foo!(a?); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:42:11
+   |
+LL |     foo!(a?a); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018-feature-gate.rs:43:11
+   |
+LL |     foo!(a?a?a); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: aborting due to 10 previous errors
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2018.rs b/src/test/ui/macros/macro-at-most-once-rep-2018.rs
new file mode 100644
index 00000000000..958a7e0cdf4
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2018.rs
@@ -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.
+
+// Tests that `?` is a Kleene op and not a macro separator in the 2018 edition.
+
+// compile-flags: --edition=2018
+
+#![feature(macro_at_most_once_rep)]
+
+macro_rules! foo {
+    ($(a)?) => {}
+}
+
+macro_rules! baz {
+    ($(a),?) => {} //~ERROR the `?` macro repetition operator
+}
+
+macro_rules! barplus {
+    ($(a)?+) => {} // ok. matches "a+" and "+"
+}
+
+macro_rules! barstar {
+    ($(a)?*) => {} // ok. matches "a*" and "*"
+}
+
+pub fn main() {
+    foo!();
+    foo!(a);
+    foo!(a?); //~ ERROR no rules expected the token `?`
+    foo!(a?a); //~ ERROR no rules expected the token `?`
+    foo!(a?a?a); //~ ERROR no rules expected the token `?`
+
+    barplus!(); //~ERROR unexpected end of macro invocation
+    barplus!(a); //~ERROR unexpected end of macro invocation
+    barplus!(a?); //~ ERROR no rules expected the token `?`
+    barplus!(a?a); //~ ERROR no rules expected the token `?`
+    barplus!(a+);
+    barplus!(+);
+
+    barstar!(); //~ERROR unexpected end of macro invocation
+    barstar!(a); //~ERROR unexpected end of macro invocation
+    barstar!(a?); //~ ERROR no rules expected the token `?`
+    barstar!(a?a); //~ ERROR no rules expected the token `?`
+    barstar!(a*);
+    barstar!(*);
+}
diff --git a/src/test/ui/macros/macro-at-most-once-rep-2018.stderr b/src/test/ui/macros/macro-at-most-once-rep-2018.stderr
new file mode 100644
index 00000000000..0a15bdb1068
--- /dev/null
+++ b/src/test/ui/macros/macro-at-most-once-rep-2018.stderr
@@ -0,0 +1,74 @@
+error: the `?` macro repetition operator does not take a separator
+  --> $DIR/macro-at-most-once-rep-2018.rs:22:10
+   |
+LL |     ($(a),?) => {} //~ERROR the `?` macro repetition operator
+   |          ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:36:11
+   |
+LL |     foo!(a?); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:37:11
+   |
+LL |     foo!(a?a); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:38:11
+   |
+LL |     foo!(a?a?a); //~ ERROR no rules expected the token `?`
+   |           ^
+
+error: unexpected end of macro invocation
+  --> $DIR/macro-at-most-once-rep-2018.rs:40:5
+   |
+LL |     barplus!(); //~ERROR unexpected end of macro invocation
+   |     ^^^^^^^^^^^
+
+error: unexpected end of macro invocation
+  --> $DIR/macro-at-most-once-rep-2018.rs:41:14
+   |
+LL |     barplus!(a); //~ERROR unexpected end of macro invocation
+   |              ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:42:15
+   |
+LL |     barplus!(a?); //~ ERROR no rules expected the token `?`
+   |               ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:43:15
+   |
+LL |     barplus!(a?a); //~ ERROR no rules expected the token `?`
+   |               ^
+
+error: unexpected end of macro invocation
+  --> $DIR/macro-at-most-once-rep-2018.rs:47:5
+   |
+LL |     barstar!(); //~ERROR unexpected end of macro invocation
+   |     ^^^^^^^^^^^
+
+error: unexpected end of macro invocation
+  --> $DIR/macro-at-most-once-rep-2018.rs:48:14
+   |
+LL |     barstar!(a); //~ERROR unexpected end of macro invocation
+   |              ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:49:15
+   |
+LL |     barstar!(a?); //~ ERROR no rules expected the token `?`
+   |               ^
+
+error: no rules expected the token `?`
+  --> $DIR/macro-at-most-once-rep-2018.rs:50:15
+   |
+LL |     barstar!(a?a); //~ ERROR no rules expected the token `?`
+   |               ^
+
+error: aborting due to 12 previous errors
+
diff --git a/src/test/ui/macros/macro-at-most-once-rep-ambig.rs b/src/test/ui/macros/macro-at-most-once-rep-ambig.rs
deleted file mode 100644
index a5660f8b41f..00000000000
--- a/src/test/ui/macros/macro-at-most-once-rep-ambig.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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
-}
diff --git a/src/test/ui/macros/macro-at-most-once-rep-ambig.stderr b/src/test/ui/macros/macro-at-most-once-rep-ambig.stderr
deleted file mode 100644
index d382082a575..00000000000
--- a/src/test/ui/macros/macro-at-most-once-rep-ambig.stderr
+++ /dev/null
@@ -1,80 +0,0 @@
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:40:11
-   |
-LL |     foo!(a?a?a); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:41:11
-   |
-LL |     foo!(a?a); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:42:11
-   |
-LL |     foo!(a?); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:43:11
-   |
-LL |     baz!(a?a?a); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:44:11
-   |
-LL |     baz!(a?a); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:45:11
-   |
-LL |     baz!(a?); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: unexpected end of macro invocation
-  --> $DIR/macro-at-most-once-rep-ambig.rs:46:11
-   |
-LL |     baz!(a,); //~ ERROR unexpected end of macro invocation
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:47:11
-   |
-LL |     baz!(a?a?a,); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:48:11
-   |
-LL |     baz!(a?a,); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: no rules expected the token `?`
-  --> $DIR/macro-at-most-once-rep-ambig.rs:49:11
-   |
-LL |     baz!(a?,); //~ ERROR no rules expected the token `?`
-   |           ^
-
-error: unexpected end of macro invocation
-  --> $DIR/macro-at-most-once-rep-ambig.rs:50:5
-   |
-LL |     barplus!(); //~ ERROR unexpected end of macro invocation
-   |     ^^^^^^^^^^^
-
-error: unexpected end of macro invocation
-  --> $DIR/macro-at-most-once-rep-ambig.rs:51:15
-   |
-LL |     barplus!(a?); //~ ERROR unexpected end of macro invocation
-   |               ^
-
-error: unexpected end of macro invocation
-  --> $DIR/macro-at-most-once-rep-ambig.rs:52:15
-   |
-LL |     barstar!(a?); //~ ERROR unexpected end of macro invocation
-   |               ^
-
-error: aborting due to 13 previous errors
-