about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-02-12 12:10:10 +0000
committerbors <bors@rust-lang.org>2019-02-12 12:10:10 +0000
commitc84e7976423bb910bb5eb5eecffc7e33a897a97f (patch)
tree24570884cb7742fe817286a3cde2643722eb8c6a
parenta54b5c7a645ead203d77e78245362f9e0f00dd3c (diff)
parentbbe524d7c1a1028737a93c7c71c508a68363b681 (diff)
downloadrust-c84e7976423bb910bb5eb5eecffc7e33a897a97f.tar.gz
rust-c84e7976423bb910bb5eb5eecffc7e33a897a97f.zip
Auto merge of #58098 - oli-obk:maybe_allow_internal_unstable, r=petrochenkov
Require a list of features in `#[allow_internal_unstable]`

The blanket-permission slip is not great and will likely give us trouble some point down the road.
-rw-r--r--src/liballoc/macros.rs3
-rw-r--r--src/libcore/macros.rs6
-rw-r--r--src/librustc/hir/lowering.rs44
-rw-r--r--src/librustc/middle/stability.rs9
-rw-r--r--src/librustc_allocator/expand.rs4
-rw-r--r--src/librustc_data_structures/macros.rs3
-rw-r--r--src/librustc_metadata/creader.rs2
-rw-r--r--src/librustc_metadata/cstore_impl.rs4
-rw-r--r--src/librustc_mir/transform/qualify_consts.rs2
-rw-r--r--src/librustc_plugin/registry.rs6
-rw-r--r--src/libstd/macros.rs18
-rw-r--r--src/libstd/sync/mpsc/mod.rs3
-rw-r--r--src/libstd/sync/mpsc/select.rs416
-rw-r--r--src/libstd/sync/mpsc/select_tests.rs413
-rw-r--r--src/libstd/thread/local.rs8
-rw-r--r--src/libsyntax/ext/base.rs20
-rw-r--r--src/libsyntax/ext/derive.rs5
-rw-r--r--src/libsyntax/ext/expand.rs28
-rw-r--r--src/libsyntax/ext/source_util.rs2
-rw-r--r--src/libsyntax/ext/tt/macro_rules.rs19
-rw-r--r--src/libsyntax/feature_gate.rs25
-rw-r--r--src/libsyntax/std_inject.rs4
-rw-r--r--src/libsyntax/test.rs6
-rw-r--r--src/libsyntax_ext/deriving/mod.rs9
-rw-r--r--src/libsyntax_ext/format.rs2
-rw-r--r--src/libsyntax_ext/lib.rs10
-rw-r--r--src/libsyntax_ext/proc_macro_decls.rs5
-rw-r--r--src/libsyntax_ext/test.rs5
-rw-r--r--src/libsyntax_ext/test_case.rs5
-rw-r--r--src/libsyntax_pos/hygiene.rs7
-rw-r--r--src/libsyntax_pos/lib.rs8
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/plugin_args.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.stderr4
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.stderr4
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-allow-internal-unstable.stderr4
-rw-r--r--src/test/ui/internal/auxiliary/internal_unstable.rs10
-rw-r--r--src/test/ui/internal/internal-unstable.rs2
-rw-r--r--src/tools/tidy/src/pal.rs1
41 files changed, 622 insertions, 512 deletions
diff --git a/src/liballoc/macros.rs b/src/liballoc/macros.rs
index db91b07fa71..7ae57a8dc79 100644
--- a/src/liballoc/macros.rs
+++ b/src/liballoc/macros.rs
@@ -34,7 +34,8 @@
 #[cfg(not(test))]
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(not(stage0), allow_internal_unstable(box_syntax))]
+#[cfg_attr(stage0, allow_internal_unstable)]
 macro_rules! vec {
     ($elem:expr; $n:expr) => (
         $crate::vec::from_elem($elem, $n)
diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs
index 664490c1997..6b5fe84ff61 100644
--- a/src/libcore/macros.rs
+++ b/src/libcore/macros.rs
@@ -1,6 +1,7 @@
 /// Entry point of thread panic, for details, see std::macros
 #[macro_export]
-#[allow_internal_unstable]
+#[cfg_attr(not(stage0), allow_internal_unstable(core_panic, __rust_unstable_column))]
+#[cfg_attr(stage0, allow_internal_unstable)]
 #[stable(feature = "core", since = "1.6.0")]
 macro_rules! panic {
     () => (
@@ -409,7 +410,8 @@ macro_rules! write {
 /// ```
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(format_args_nl))]
 macro_rules! writeln {
     ($dst:expr) => (
         write!($dst, "\n")
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 3de41b1665d..43c756def88 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -44,6 +44,7 @@ use crate::middle::cstore::CrateStore;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::indexed_vec::IndexVec;
 use rustc_data_structures::thin_vec::ThinVec;
+use rustc_data_structures::sync::Lrc;
 use crate::session::Session;
 use crate::session::config::nightly_options;
 use crate::util::common::FN_OUTPUT_NAME;
@@ -681,13 +682,20 @@ impl<'a> LoweringContext<'a> {
         Ident::with_empty_ctxt(Symbol::gensym(s))
     }
 
-    fn allow_internal_unstable(&self, reason: CompilerDesugaringKind, span: Span) -> Span {
+    /// Reuses the span but adds information like the kind of the desugaring and features that are
+    /// allowed inside this span.
+    fn mark_span_with_reason(
+        &self,
+        reason: CompilerDesugaringKind,
+        span: Span,
+        allow_internal_unstable: Option<Lrc<[Symbol]>>,
+    ) -> Span {
         let mark = Mark::fresh(Mark::root());
         mark.set_expn_info(source_map::ExpnInfo {
             call_site: span,
             def_site: Some(span),
             format: source_map::CompilerDesugaring(reason),
-            allow_internal_unstable: true,
+            allow_internal_unstable,
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: source_map::hygiene::default_edition(),
@@ -964,7 +972,13 @@ impl<'a> LoweringContext<'a> {
             attrs: ThinVec::new(),
         };
 
-        let unstable_span = self.allow_internal_unstable(CompilerDesugaringKind::Async, span);
+        let unstable_span = self.mark_span_with_reason(
+            CompilerDesugaringKind::Async,
+            span,
+            Some(vec![
+                Symbol::intern("gen_future"),
+            ].into()),
+        );
         let gen_future = self.expr_std_path(
             unstable_span, &["future", "from_generator"], None, ThinVec::new());
         hir::ExprKind::Call(P(gen_future), hir_vec![generator])
@@ -1360,9 +1374,10 @@ impl<'a> LoweringContext<'a> {
         // desugaring that explicitly states that we don't want to track that.
         // Not tracking it makes lints in rustc and clippy very fragile as
         // frequently opened issues show.
-        let exist_ty_span = self.allow_internal_unstable(
+        let exist_ty_span = self.mark_span_with_reason(
             CompilerDesugaringKind::ExistentialReturnType,
             span,
+            None,
         );
 
         let exist_ty_def_index = self
@@ -3927,8 +3942,13 @@ impl<'a> LoweringContext<'a> {
             }),
             ExprKind::TryBlock(ref body) => {
                 self.with_catch_scope(body.id, |this| {
-                    let unstable_span =
-                        this.allow_internal_unstable(CompilerDesugaringKind::TryBlock, body.span);
+                    let unstable_span = this.mark_span_with_reason(
+                        CompilerDesugaringKind::TryBlock,
+                        body.span,
+                        Some(vec![
+                            Symbol::intern("try_trait"),
+                        ].into()),
+                    );
                     let mut block = this.lower_block(body, true).into_inner();
                     let tail = block.expr.take().map_or_else(
                         || {
@@ -4360,9 +4380,10 @@ impl<'a> LoweringContext<'a> {
                 // expand <head>
                 let head = self.lower_expr(head);
                 let head_sp = head.span;
-                let desugared_span = self.allow_internal_unstable(
+                let desugared_span = self.mark_span_with_reason(
                     CompilerDesugaringKind::ForLoop,
                     head_sp,
+                    None,
                 );
 
                 let iter = self.str_to_ident("iter");
@@ -4525,8 +4546,13 @@ impl<'a> LoweringContext<'a> {
                 //                 return Try::from_error(From::from(err)),
                 // }
 
-                let unstable_span =
-                    self.allow_internal_unstable(CompilerDesugaringKind::QuestionMark, e.span);
+                let unstable_span = self.mark_span_with_reason(
+                    CompilerDesugaringKind::QuestionMark,
+                    e.span,
+                    Some(vec![
+                        Symbol::intern("try_trait")
+                    ].into()),
+                );
 
                 // `Try::into_result(<expr>)`
                 let discr = {
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 34c77d08f5a..5d606abb3cd 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -561,11 +561,6 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
     /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
     /// `id`.
     pub fn eval_stability(self, def_id: DefId, id: Option<NodeId>, span: Span) -> EvalResult {
-        if span.allows_unstable() {
-            debug!("stability: skipping span={:?} since it is internal", span);
-            return EvalResult::Allow;
-        }
-
         let lint_deprecated = |def_id: DefId,
                                id: NodeId,
                                note: Option<Symbol>,
@@ -694,6 +689,10 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
 
         match stability {
             Some(&Stability { level: attr::Unstable { reason, issue }, feature, .. }) => {
+                if span.allows_unstable(&feature.as_str()) {
+                    debug!("stability: skipping span={:?} since it is internal", span);
+                    return EvalResult::Allow;
+                }
                 if self.stability().active_features.contains(&feature) {
                     return EvalResult::Allow;
                 }
diff --git a/src/librustc_allocator/expand.rs b/src/librustc_allocator/expand.rs
index d302e7646d1..758a0d63886 100644
--- a/src/librustc_allocator/expand.rs
+++ b/src/librustc_allocator/expand.rs
@@ -91,7 +91,9 @@ impl MutVisitor for ExpandAllocatorDirectives<'_> {
             call_site: item.span, // use the call site of the static
             def_site: None,
             format: MacroAttribute(Symbol::intern(name)),
-            allow_internal_unstable: true,
+            allow_internal_unstable: Some(vec![
+                Symbol::intern("rustc_attrs"),
+            ].into()),
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: hygiene::default_edition(),
diff --git a/src/librustc_data_structures/macros.rs b/src/librustc_data_structures/macros.rs
index a661f0c78ab..af1f2910461 100644
--- a/src/librustc_data_structures/macros.rs
+++ b/src/librustc_data_structures/macros.rs
@@ -1,7 +1,8 @@
 /// A simple static assertion macro. The first argument should be a unique
 /// ALL_CAPS identifier that describes the condition.
 #[macro_export]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(type_ascription))]
 macro_rules! static_assert {
     ($name:ident: $test:expr) => {
         // Use the bool to access an array such that if the bool is false, the access
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 0b4c8a5367c..90b0fb249aa 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -570,7 +570,7 @@ impl<'a> CrateLoader<'a> {
                 ProcMacro::Bang { name, client } => {
                     (name, SyntaxExtension::ProcMacro {
                         expander: Box::new(BangProcMacro { client }),
-                        allow_internal_unstable: false,
+                        allow_internal_unstable: None,
                         edition: root.edition,
                     })
                 }
diff --git a/src/librustc_metadata/cstore_impl.rs b/src/librustc_metadata/cstore_impl.rs
index b248c6bf656..f49b88f14e6 100644
--- a/src/librustc_metadata/cstore_impl.rs
+++ b/src/librustc_metadata/cstore_impl.rs
@@ -425,7 +425,9 @@ impl cstore::CStore {
             let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
             let ext = SyntaxExtension::ProcMacro {
                 expander: Box::new(BangProcMacro { client }),
-                allow_internal_unstable: true,
+                allow_internal_unstable: Some(vec![
+                    Symbol::intern("proc_macro_def_site"),
+                ].into()),
                 edition: data.root.edition,
             };
             return LoadedMacro::ProcMacro(Lrc::new(ext));
diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs
index b116c327f5b..c553398b9b1 100644
--- a/src/librustc_mir/transform/qualify_consts.rs
+++ b/src/librustc_mir/transform/qualify_consts.rs
@@ -909,7 +909,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
                                     // Check `#[unstable]` const fns or `#[rustc_const_unstable]`
                                     // functions without the feature gate active in this crate in
                                     // order to report a better error message than the one below.
-                                    if self.span.allows_unstable() {
+                                    if self.span.allows_unstable(&feature.as_str()) {
                                         // `allow_internal_unstable` can make such calls stable.
                                         is_const_fn = true;
                                     } else {
diff --git a/src/librustc_plugin/registry.rs b/src/librustc_plugin/registry.rs
index b53d956a9c0..2da520d3969 100644
--- a/src/librustc_plugin/registry.rs
+++ b/src/librustc_plugin/registry.rs
@@ -110,8 +110,8 @@ impl<'a> Registry<'a> {
                     edition,
                 }
             }
-            IdentTT(ext, _, allow_internal_unstable) => {
-                IdentTT(ext, Some(self.krate_span), allow_internal_unstable)
+            IdentTT { expander, span: _, allow_internal_unstable } => {
+                IdentTT { expander, span: Some(self.krate_span), allow_internal_unstable }
             }
             _ => extension,
         }));
@@ -126,7 +126,7 @@ impl<'a> Registry<'a> {
         self.register_syntax_extension(Symbol::intern(name), NormalTT {
             expander: Box::new(expander),
             def_info: None,
-            allow_internal_unstable: false,
+            allow_internal_unstable: None,
             allow_internal_unsafe: false,
             local_inner_macros: false,
             unstable_feature: None,
diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs
index b87257188df..506b6d4e8e0 100644
--- a/src/libstd/macros.rs
+++ b/src/libstd/macros.rs
@@ -53,7 +53,8 @@
 /// ```
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(__rust_unstable_column, libstd_sys_internals))]
 macro_rules! panic {
     () => ({
         panic!("explicit panic")
@@ -111,7 +112,8 @@ macro_rules! panic {
 /// ```
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(print_internals))]
 macro_rules! print {
     ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
 }
@@ -143,7 +145,8 @@ macro_rules! print {
 /// ```
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(print_internals, format_args_nl))]
 macro_rules! println {
     () => (print!("\n"));
     ($($arg:tt)*) => ({
@@ -174,7 +177,8 @@ macro_rules! println {
 /// ```
 #[macro_export]
 #[stable(feature = "eprint", since = "1.19.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(print_internals))]
 macro_rules! eprint {
     ($($arg:tt)*) => ($crate::io::_eprint(format_args!($($arg)*)));
 }
@@ -202,7 +206,8 @@ macro_rules! eprint {
 /// ```
 #[macro_export]
 #[stable(feature = "eprint", since = "1.19.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(print_internals, format_args_nl))]
 macro_rules! eprintln {
     () => (eprint!("\n"));
     ($($arg:tt)*) => ({
@@ -325,7 +330,8 @@ macro_rules! dbg {
 /// A macro to await on an async call.
 #[macro_export]
 #[unstable(feature = "await_macro", issue = "50547")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(gen_future, generators))]
 #[allow_internal_unsafe]
 macro_rules! await {
     ($e:expr) => { {
diff --git a/src/libstd/sync/mpsc/mod.rs b/src/libstd/sync/mpsc/mod.rs
index 446c164965d..a8843549b8a 100644
--- a/src/libstd/sync/mpsc/mod.rs
+++ b/src/libstd/sync/mpsc/mod.rs
@@ -279,6 +279,9 @@ use self::select::StartResult;
 use self::select::StartResult::*;
 use self::blocking::SignalToken;
 
+#[cfg(all(test, not(target_os = "emscripten")))]
+mod select_tests;
+
 mod blocking;
 mod oneshot;
 mod select;
diff --git a/src/libstd/sync/mpsc/select.rs b/src/libstd/sync/mpsc/select.rs
index 8f41680a818..e34fc5487cf 100644
--- a/src/libstd/sync/mpsc/select.rs
+++ b/src/libstd/sync/mpsc/select.rs
@@ -158,7 +158,7 @@ impl Select {
     }
 
     /// Helper method for skipping the preflight checks during testing
-    fn wait2(&self, do_preflight_checks: bool) -> usize {
+    pub(super) fn wait2(&self, do_preflight_checks: bool) -> usize {
         // Note that this is currently an inefficient implementation. We in
         // theory have knowledge about all receivers in the set ahead of time,
         // so this method shouldn't really have to iterate over all of them yet
@@ -352,417 +352,3 @@ impl<'rx, T:Send+'rx> fmt::Debug for Handle<'rx, T> {
         f.debug_struct("Handle").finish()
     }
 }
-
-#[allow(unused_imports)]
-#[cfg(all(test, not(target_os = "emscripten")))]
-mod tests {
-    use thread;
-    use sync::mpsc::*;
-
-    // Don't use the libstd version so we can pull in the right Select structure
-    // (std::comm points at the wrong one)
-    macro_rules! select {
-        (
-            $($name:pat = $rx:ident.$meth:ident() => $code:expr),+
-        ) => ({
-            let sel = Select::new();
-            $( let mut $rx = sel.handle(&$rx); )+
-            unsafe {
-                $( $rx.add(); )+
-            }
-            let ret = sel.wait();
-            $( if ret == $rx.id() { let $name = $rx.$meth(); $code } else )+
-            { unreachable!() }
-        })
-    }
-
-    #[test]
-    fn smoke() {
-        let (tx1, rx1) = channel::<i32>();
-        let (tx2, rx2) = channel::<i32>();
-        tx1.send(1).unwrap();
-        select! {
-            foo = rx1.recv() => { assert_eq!(foo.unwrap(), 1); },
-            _bar = rx2.recv() => { panic!() }
-        }
-        tx2.send(2).unwrap();
-        select! {
-            _foo = rx1.recv() => { panic!() },
-            bar = rx2.recv() => { assert_eq!(bar.unwrap(), 2) }
-        }
-        drop(tx1);
-        select! {
-            foo = rx1.recv() => { assert!(foo.is_err()); },
-            _bar = rx2.recv() => { panic!() }
-        }
-        drop(tx2);
-        select! {
-            bar = rx2.recv() => { assert!(bar.is_err()); }
-        }
-    }
-
-    #[test]
-    fn smoke2() {
-        let (_tx1, rx1) = channel::<i32>();
-        let (_tx2, rx2) = channel::<i32>();
-        let (_tx3, rx3) = channel::<i32>();
-        let (_tx4, rx4) = channel::<i32>();
-        let (tx5, rx5) = channel::<i32>();
-        tx5.send(4).unwrap();
-        select! {
-            _foo = rx1.recv() => { panic!("1") },
-            _foo = rx2.recv() => { panic!("2") },
-            _foo = rx3.recv() => { panic!("3") },
-            _foo = rx4.recv() => { panic!("4") },
-            foo = rx5.recv() => { assert_eq!(foo.unwrap(), 4); }
-        }
-    }
-
-    #[test]
-    fn closed() {
-        let (_tx1, rx1) = channel::<i32>();
-        let (tx2, rx2) = channel::<i32>();
-        drop(tx2);
-
-        select! {
-            _a1 = rx1.recv() => { panic!() },
-            a2 = rx2.recv() => { assert!(a2.is_err()); }
-        }
-    }
-
-    #[test]
-    fn unblocks() {
-        let (tx1, rx1) = channel::<i32>();
-        let (_tx2, rx2) = channel::<i32>();
-        let (tx3, rx3) = channel::<i32>();
-
-        let _t = thread::spawn(move|| {
-            for _ in 0..20 { thread::yield_now(); }
-            tx1.send(1).unwrap();
-            rx3.recv().unwrap();
-            for _ in 0..20 { thread::yield_now(); }
-        });
-
-        select! {
-            a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
-            _b = rx2.recv() => { panic!() }
-        }
-        tx3.send(1).unwrap();
-        select! {
-            a = rx1.recv() => { assert!(a.is_err()) },
-            _b = rx2.recv() => { panic!() }
-        }
-    }
-
-    #[test]
-    fn both_ready() {
-        let (tx1, rx1) = channel::<i32>();
-        let (tx2, rx2) = channel::<i32>();
-        let (tx3, rx3) = channel::<()>();
-
-        let _t = thread::spawn(move|| {
-            for _ in 0..20 { thread::yield_now(); }
-            tx1.send(1).unwrap();
-            tx2.send(2).unwrap();
-            rx3.recv().unwrap();
-        });
-
-        select! {
-            a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
-            a = rx2.recv() => { assert_eq!(a.unwrap(), 2); }
-        }
-        select! {
-            a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
-            a = rx2.recv() => { assert_eq!(a.unwrap(), 2); }
-        }
-        assert_eq!(rx1.try_recv(), Err(TryRecvError::Empty));
-        assert_eq!(rx2.try_recv(), Err(TryRecvError::Empty));
-        tx3.send(()).unwrap();
-    }
-
-    #[test]
-    fn stress() {
-        const AMT: i32 = 10000;
-        let (tx1, rx1) = channel::<i32>();
-        let (tx2, rx2) = channel::<i32>();
-        let (tx3, rx3) = channel::<()>();
-
-        let _t = thread::spawn(move|| {
-            for i in 0..AMT {
-                if i % 2 == 0 {
-                    tx1.send(i).unwrap();
-                } else {
-                    tx2.send(i).unwrap();
-                }
-                rx3.recv().unwrap();
-            }
-        });
-
-        for i in 0..AMT {
-            select! {
-                i1 = rx1.recv() => { assert!(i % 2 == 0 && i == i1.unwrap()); },
-                i2 = rx2.recv() => { assert!(i % 2 == 1 && i == i2.unwrap()); }
-            }
-            tx3.send(()).unwrap();
-        }
-    }
-
-    #[allow(unused_must_use)]
-    #[test]
-    fn cloning() {
-        let (tx1, rx1) = channel::<i32>();
-        let (_tx2, rx2) = channel::<i32>();
-        let (tx3, rx3) = channel::<()>();
-
-        let _t = thread::spawn(move|| {
-            rx3.recv().unwrap();
-            tx1.clone();
-            assert_eq!(rx3.try_recv(), Err(TryRecvError::Empty));
-            tx1.send(2).unwrap();
-            rx3.recv().unwrap();
-        });
-
-        tx3.send(()).unwrap();
-        select! {
-            _i1 = rx1.recv() => {},
-            _i2 = rx2.recv() => panic!()
-        }
-        tx3.send(()).unwrap();
-    }
-
-    #[allow(unused_must_use)]
-    #[test]
-    fn cloning2() {
-        let (tx1, rx1) = channel::<i32>();
-        let (_tx2, rx2) = channel::<i32>();
-        let (tx3, rx3) = channel::<()>();
-
-        let _t = thread::spawn(move|| {
-            rx3.recv().unwrap();
-            tx1.clone();
-            assert_eq!(rx3.try_recv(), Err(TryRecvError::Empty));
-            tx1.send(2).unwrap();
-            rx3.recv().unwrap();
-        });
-
-        tx3.send(()).unwrap();
-        select! {
-            _i1 = rx1.recv() => {},
-            _i2 = rx2.recv() => panic!()
-        }
-        tx3.send(()).unwrap();
-    }
-
-    #[test]
-    fn cloning3() {
-        let (tx1, rx1) = channel::<()>();
-        let (tx2, rx2) = channel::<()>();
-        let (tx3, rx3) = channel::<()>();
-        let _t = thread::spawn(move|| {
-            let s = Select::new();
-            let mut h1 = s.handle(&rx1);
-            let mut h2 = s.handle(&rx2);
-            unsafe { h2.add(); }
-            unsafe { h1.add(); }
-            assert_eq!(s.wait(), h2.id);
-            tx3.send(()).unwrap();
-        });
-
-        for _ in 0..1000 { thread::yield_now(); }
-        drop(tx1.clone());
-        tx2.send(()).unwrap();
-        rx3.recv().unwrap();
-    }
-
-    #[test]
-    fn preflight1() {
-        let (tx, rx) = channel();
-        tx.send(()).unwrap();
-        select! {
-            _n = rx.recv() => {}
-        }
-    }
-
-    #[test]
-    fn preflight2() {
-        let (tx, rx) = channel();
-        tx.send(()).unwrap();
-        tx.send(()).unwrap();
-        select! {
-            _n = rx.recv() => {}
-        }
-    }
-
-    #[test]
-    fn preflight3() {
-        let (tx, rx) = channel();
-        drop(tx.clone());
-        tx.send(()).unwrap();
-        select! {
-            _n = rx.recv() => {}
-        }
-    }
-
-    #[test]
-    fn preflight4() {
-        let (tx, rx) = channel();
-        tx.send(()).unwrap();
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn preflight5() {
-        let (tx, rx) = channel();
-        tx.send(()).unwrap();
-        tx.send(()).unwrap();
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn preflight6() {
-        let (tx, rx) = channel();
-        drop(tx.clone());
-        tx.send(()).unwrap();
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn preflight7() {
-        let (tx, rx) = channel::<()>();
-        drop(tx);
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn preflight8() {
-        let (tx, rx) = channel();
-        tx.send(()).unwrap();
-        drop(tx);
-        rx.recv().unwrap();
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn preflight9() {
-        let (tx, rx) = channel();
-        drop(tx.clone());
-        tx.send(()).unwrap();
-        drop(tx);
-        rx.recv().unwrap();
-        let s = Select::new();
-        let mut h = s.handle(&rx);
-        unsafe { h.add(); }
-        assert_eq!(s.wait2(false), h.id);
-    }
-
-    #[test]
-    fn oneshot_data_waiting() {
-        let (tx1, rx1) = channel();
-        let (tx2, rx2) = channel();
-        let _t = thread::spawn(move|| {
-            select! {
-                _n = rx1.recv() => {}
-            }
-            tx2.send(()).unwrap();
-        });
-
-        for _ in 0..100 { thread::yield_now() }
-        tx1.send(()).unwrap();
-        rx2.recv().unwrap();
-    }
-
-    #[test]
-    fn stream_data_waiting() {
-        let (tx1, rx1) = channel();
-        let (tx2, rx2) = channel();
-        tx1.send(()).unwrap();
-        tx1.send(()).unwrap();
-        rx1.recv().unwrap();
-        rx1.recv().unwrap();
-        let _t = thread::spawn(move|| {
-            select! {
-                _n = rx1.recv() => {}
-            }
-            tx2.send(()).unwrap();
-        });
-
-        for _ in 0..100 { thread::yield_now() }
-        tx1.send(()).unwrap();
-        rx2.recv().unwrap();
-    }
-
-    #[test]
-    fn shared_data_waiting() {
-        let (tx1, rx1) = channel();
-        let (tx2, rx2) = channel();
-        drop(tx1.clone());
-        tx1.send(()).unwrap();
-        rx1.recv().unwrap();
-        let _t = thread::spawn(move|| {
-            select! {
-                _n = rx1.recv() => {}
-            }
-            tx2.send(()).unwrap();
-        });
-
-        for _ in 0..100 { thread::yield_now() }
-        tx1.send(()).unwrap();
-        rx2.recv().unwrap();
-    }
-
-    #[test]
-    fn sync1() {
-        let (tx, rx) = sync_channel::<i32>(1);
-        tx.send(1).unwrap();
-        select! {
-            n = rx.recv() => { assert_eq!(n.unwrap(), 1); }
-        }
-    }
-
-    #[test]
-    fn sync2() {
-        let (tx, rx) = sync_channel::<i32>(0);
-        let _t = thread::spawn(move|| {
-            for _ in 0..100 { thread::yield_now() }
-            tx.send(1).unwrap();
-        });
-        select! {
-            n = rx.recv() => { assert_eq!(n.unwrap(), 1); }
-        }
-    }
-
-    #[test]
-    fn sync3() {
-        let (tx1, rx1) = sync_channel::<i32>(0);
-        let (tx2, rx2): (Sender<i32>, Receiver<i32>) = channel();
-        let _t = thread::spawn(move|| { tx1.send(1).unwrap(); });
-        let _t = thread::spawn(move|| { tx2.send(2).unwrap(); });
-        select! {
-            n = rx1.recv() => {
-                let n = n.unwrap();
-                assert_eq!(n, 1);
-                assert_eq!(rx2.recv().unwrap(), 2);
-            },
-            n = rx2.recv() => {
-                let n = n.unwrap();
-                assert_eq!(n, 2);
-                assert_eq!(rx1.recv().unwrap(), 1);
-            }
-        }
-    }
-}
diff --git a/src/libstd/sync/mpsc/select_tests.rs b/src/libstd/sync/mpsc/select_tests.rs
new file mode 100644
index 00000000000..be048511caa
--- /dev/null
+++ b/src/libstd/sync/mpsc/select_tests.rs
@@ -0,0 +1,413 @@
+#![allow(unused_imports)]
+
+/// This file exists to hack around https://github.com/rust-lang/rust/issues/47238
+
+use thread;
+use sync::mpsc::*;
+
+// Don't use the libstd version so we can pull in the right Select structure
+// (std::comm points at the wrong one)
+macro_rules! select {
+    (
+        $($name:pat = $rx:ident.$meth:ident() => $code:expr),+
+    ) => ({
+        let sel = Select::new();
+        $( let mut $rx = sel.handle(&$rx); )+
+        unsafe {
+            $( $rx.add(); )+
+        }
+        let ret = sel.wait();
+        $( if ret == $rx.id() { let $name = $rx.$meth(); $code } else )+
+        { unreachable!() }
+    })
+}
+
+#[test]
+fn smoke() {
+    let (tx1, rx1) = channel::<i32>();
+    let (tx2, rx2) = channel::<i32>();
+    tx1.send(1).unwrap();
+    select! {
+        foo = rx1.recv() => { assert_eq!(foo.unwrap(), 1); },
+        _bar = rx2.recv() => { panic!() }
+    }
+    tx2.send(2).unwrap();
+    select! {
+        _foo = rx1.recv() => { panic!() },
+        bar = rx2.recv() => { assert_eq!(bar.unwrap(), 2) }
+    }
+    drop(tx1);
+    select! {
+        foo = rx1.recv() => { assert!(foo.is_err()); },
+        _bar = rx2.recv() => { panic!() }
+    }
+    drop(tx2);
+    select! {
+        bar = rx2.recv() => { assert!(bar.is_err()); }
+    }
+}
+
+#[test]
+fn smoke2() {
+    let (_tx1, rx1) = channel::<i32>();
+    let (_tx2, rx2) = channel::<i32>();
+    let (_tx3, rx3) = channel::<i32>();
+    let (_tx4, rx4) = channel::<i32>();
+    let (tx5, rx5) = channel::<i32>();
+    tx5.send(4).unwrap();
+    select! {
+        _foo = rx1.recv() => { panic!("1") },
+        _foo = rx2.recv() => { panic!("2") },
+        _foo = rx3.recv() => { panic!("3") },
+        _foo = rx4.recv() => { panic!("4") },
+        foo = rx5.recv() => { assert_eq!(foo.unwrap(), 4); }
+    }
+}
+
+#[test]
+fn closed() {
+    let (_tx1, rx1) = channel::<i32>();
+    let (tx2, rx2) = channel::<i32>();
+    drop(tx2);
+
+    select! {
+        _a1 = rx1.recv() => { panic!() },
+        a2 = rx2.recv() => { assert!(a2.is_err()); }
+    }
+}
+
+#[test]
+fn unblocks() {
+    let (tx1, rx1) = channel::<i32>();
+    let (_tx2, rx2) = channel::<i32>();
+    let (tx3, rx3) = channel::<i32>();
+
+    let _t = thread::spawn(move|| {
+        for _ in 0..20 { thread::yield_now(); }
+        tx1.send(1).unwrap();
+        rx3.recv().unwrap();
+        for _ in 0..20 { thread::yield_now(); }
+    });
+
+    select! {
+        a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
+        _b = rx2.recv() => { panic!() }
+    }
+    tx3.send(1).unwrap();
+    select! {
+        a = rx1.recv() => { assert!(a.is_err()) },
+        _b = rx2.recv() => { panic!() }
+    }
+}
+
+#[test]
+fn both_ready() {
+    let (tx1, rx1) = channel::<i32>();
+    let (tx2, rx2) = channel::<i32>();
+    let (tx3, rx3) = channel::<()>();
+
+    let _t = thread::spawn(move|| {
+        for _ in 0..20 { thread::yield_now(); }
+        tx1.send(1).unwrap();
+        tx2.send(2).unwrap();
+        rx3.recv().unwrap();
+    });
+
+    select! {
+        a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
+        a = rx2.recv() => { assert_eq!(a.unwrap(), 2); }
+    }
+    select! {
+        a = rx1.recv() => { assert_eq!(a.unwrap(), 1); },
+        a = rx2.recv() => { assert_eq!(a.unwrap(), 2); }
+    }
+    assert_eq!(rx1.try_recv(), Err(TryRecvError::Empty));
+    assert_eq!(rx2.try_recv(), Err(TryRecvError::Empty));
+    tx3.send(()).unwrap();
+}
+
+#[test]
+fn stress() {
+    const AMT: i32 = 10000;
+    let (tx1, rx1) = channel::<i32>();
+    let (tx2, rx2) = channel::<i32>();
+    let (tx3, rx3) = channel::<()>();
+
+    let _t = thread::spawn(move|| {
+        for i in 0..AMT {
+            if i % 2 == 0 {
+                tx1.send(i).unwrap();
+            } else {
+                tx2.send(i).unwrap();
+            }
+            rx3.recv().unwrap();
+        }
+    });
+
+    for i in 0..AMT {
+        select! {
+            i1 = rx1.recv() => { assert!(i % 2 == 0 && i == i1.unwrap()); },
+            i2 = rx2.recv() => { assert!(i % 2 == 1 && i == i2.unwrap()); }
+        }
+        tx3.send(()).unwrap();
+    }
+}
+
+#[allow(unused_must_use)]
+#[test]
+fn cloning() {
+    let (tx1, rx1) = channel::<i32>();
+    let (_tx2, rx2) = channel::<i32>();
+    let (tx3, rx3) = channel::<()>();
+
+    let _t = thread::spawn(move|| {
+        rx3.recv().unwrap();
+        tx1.clone();
+        assert_eq!(rx3.try_recv(), Err(TryRecvError::Empty));
+        tx1.send(2).unwrap();
+        rx3.recv().unwrap();
+    });
+
+    tx3.send(()).unwrap();
+    select! {
+        _i1 = rx1.recv() => {},
+        _i2 = rx2.recv() => panic!()
+    }
+    tx3.send(()).unwrap();
+}
+
+#[allow(unused_must_use)]
+#[test]
+fn cloning2() {
+    let (tx1, rx1) = channel::<i32>();
+    let (_tx2, rx2) = channel::<i32>();
+    let (tx3, rx3) = channel::<()>();
+
+    let _t = thread::spawn(move|| {
+        rx3.recv().unwrap();
+        tx1.clone();
+        assert_eq!(rx3.try_recv(), Err(TryRecvError::Empty));
+        tx1.send(2).unwrap();
+        rx3.recv().unwrap();
+    });
+
+    tx3.send(()).unwrap();
+    select! {
+        _i1 = rx1.recv() => {},
+        _i2 = rx2.recv() => panic!()
+    }
+    tx3.send(()).unwrap();
+}
+
+#[test]
+fn cloning3() {
+    let (tx1, rx1) = channel::<()>();
+    let (tx2, rx2) = channel::<()>();
+    let (tx3, rx3) = channel::<()>();
+    let _t = thread::spawn(move|| {
+        let s = Select::new();
+        let mut h1 = s.handle(&rx1);
+        let mut h2 = s.handle(&rx2);
+        unsafe { h2.add(); }
+        unsafe { h1.add(); }
+        assert_eq!(s.wait(), h2.id());
+        tx3.send(()).unwrap();
+    });
+
+    for _ in 0..1000 { thread::yield_now(); }
+    drop(tx1.clone());
+    tx2.send(()).unwrap();
+    rx3.recv().unwrap();
+}
+
+#[test]
+fn preflight1() {
+    let (tx, rx) = channel();
+    tx.send(()).unwrap();
+    select! {
+        _n = rx.recv() => {}
+    }
+}
+
+#[test]
+fn preflight2() {
+    let (tx, rx) = channel();
+    tx.send(()).unwrap();
+    tx.send(()).unwrap();
+    select! {
+        _n = rx.recv() => {}
+    }
+}
+
+#[test]
+fn preflight3() {
+    let (tx, rx) = channel();
+    drop(tx.clone());
+    tx.send(()).unwrap();
+    select! {
+        _n = rx.recv() => {}
+    }
+}
+
+#[test]
+fn preflight4() {
+    let (tx, rx) = channel();
+    tx.send(()).unwrap();
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn preflight5() {
+    let (tx, rx) = channel();
+    tx.send(()).unwrap();
+    tx.send(()).unwrap();
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn preflight6() {
+    let (tx, rx) = channel();
+    drop(tx.clone());
+    tx.send(()).unwrap();
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn preflight7() {
+    let (tx, rx) = channel::<()>();
+    drop(tx);
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn preflight8() {
+    let (tx, rx) = channel();
+    tx.send(()).unwrap();
+    drop(tx);
+    rx.recv().unwrap();
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn preflight9() {
+    let (tx, rx) = channel();
+    drop(tx.clone());
+    tx.send(()).unwrap();
+    drop(tx);
+    rx.recv().unwrap();
+    let s = Select::new();
+    let mut h = s.handle(&rx);
+    unsafe { h.add(); }
+    assert_eq!(s.wait2(false), h.id());
+}
+
+#[test]
+fn oneshot_data_waiting() {
+    let (tx1, rx1) = channel();
+    let (tx2, rx2) = channel();
+    let _t = thread::spawn(move|| {
+        select! {
+            _n = rx1.recv() => {}
+        }
+        tx2.send(()).unwrap();
+    });
+
+    for _ in 0..100 { thread::yield_now() }
+    tx1.send(()).unwrap();
+    rx2.recv().unwrap();
+}
+
+#[test]
+fn stream_data_waiting() {
+    let (tx1, rx1) = channel();
+    let (tx2, rx2) = channel();
+    tx1.send(()).unwrap();
+    tx1.send(()).unwrap();
+    rx1.recv().unwrap();
+    rx1.recv().unwrap();
+    let _t = thread::spawn(move|| {
+        select! {
+            _n = rx1.recv() => {}
+        }
+        tx2.send(()).unwrap();
+    });
+
+    for _ in 0..100 { thread::yield_now() }
+    tx1.send(()).unwrap();
+    rx2.recv().unwrap();
+}
+
+#[test]
+fn shared_data_waiting() {
+    let (tx1, rx1) = channel();
+    let (tx2, rx2) = channel();
+    drop(tx1.clone());
+    tx1.send(()).unwrap();
+    rx1.recv().unwrap();
+    let _t = thread::spawn(move|| {
+        select! {
+            _n = rx1.recv() => {}
+        }
+        tx2.send(()).unwrap();
+    });
+
+    for _ in 0..100 { thread::yield_now() }
+    tx1.send(()).unwrap();
+    rx2.recv().unwrap();
+}
+
+#[test]
+fn sync1() {
+    let (tx, rx) = sync_channel::<i32>(1);
+    tx.send(1).unwrap();
+    select! {
+        n = rx.recv() => { assert_eq!(n.unwrap(), 1); }
+    }
+}
+
+#[test]
+fn sync2() {
+    let (tx, rx) = sync_channel::<i32>(0);
+    let _t = thread::spawn(move|| {
+        for _ in 0..100 { thread::yield_now() }
+        tx.send(1).unwrap();
+    });
+    select! {
+        n = rx.recv() => { assert_eq!(n.unwrap(), 1); }
+    }
+}
+
+#[test]
+fn sync3() {
+    let (tx1, rx1) = sync_channel::<i32>(0);
+    let (tx2, rx2): (Sender<i32>, Receiver<i32>) = channel();
+    let _t = thread::spawn(move|| { tx1.send(1).unwrap(); });
+    let _t = thread::spawn(move|| { tx2.send(2).unwrap(); });
+    select! {
+        n = rx1.recv() => {
+            let n = n.unwrap();
+            assert_eq!(n, 1);
+            assert_eq!(rx2.recv().unwrap(), 2);
+        },
+        n = rx2.recv() => {
+            let n = n.unwrap();
+            assert_eq!(n, 2);
+            assert_eq!(rx1.recv().unwrap(), 1);
+        }
+    }
+}
diff --git a/src/libstd/thread/local.rs b/src/libstd/thread/local.rs
index 5d2eb5f8e73..8207709e1f9 100644
--- a/src/libstd/thread/local.rs
+++ b/src/libstd/thread/local.rs
@@ -126,7 +126,8 @@ impl<T: 'static> fmt::Debug for LocalKey<T> {
 /// [`std::thread::LocalKey`]: ../std/thread/struct.LocalKey.html
 #[macro_export]
 #[stable(feature = "rust1", since = "1.0.0")]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(thread_local_internals))]
 macro_rules! thread_local {
     // empty (base case for the recursion)
     () => {};
@@ -148,7 +149,10 @@ macro_rules! thread_local {
            reason = "should not be necessary",
            issue = "0")]
 #[macro_export]
-#[allow_internal_unstable]
+#[cfg_attr(stage0, allow_internal_unstable)]
+#[cfg_attr(not(stage0), allow_internal_unstable(
+    thread_local_internals, cfg_target_thread_local, thread_local,
+))]
 #[allow_internal_unsafe]
 macro_rules! __thread_local_inner {
     (@key $(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $init:expr) => {
diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs
index 465b53184dc..f7225810aca 100644
--- a/src/libsyntax/ext/base.rs
+++ b/src/libsyntax/ext/base.rs
@@ -621,7 +621,8 @@ pub enum SyntaxExtension {
     /// A function-like procedural macro. TokenStream -> TokenStream.
     ProcMacro {
         expander: Box<dyn ProcMacro + sync::Sync + sync::Send>,
-        allow_internal_unstable: bool,
+        /// Whitelist of unstable features that are treated as stable inside this macro
+        allow_internal_unstable: Option<Lrc<[Symbol]>>,
         edition: Edition,
     },
 
@@ -638,8 +639,10 @@ pub enum SyntaxExtension {
         expander: Box<dyn TTMacroExpander + sync::Sync + sync::Send>,
         def_info: Option<(ast::NodeId, Span)>,
         /// Whether the contents of the macro can
-        /// directly use `#[unstable]` things (true == yes).
-        allow_internal_unstable: bool,
+        /// directly use `#[unstable]` things.
+        ///
+        /// Only allows things that require a feature gate in the given whitelist
+        allow_internal_unstable: Option<Lrc<[Symbol]>>,
         /// Whether the contents of the macro can use `unsafe`
         /// without triggering the `unsafe_code` lint.
         allow_internal_unsafe: bool,
@@ -654,8 +657,11 @@ pub enum SyntaxExtension {
 
     /// A function-like syntax extension that has an extra ident before
     /// the block.
-    ///
-    IdentTT(Box<dyn IdentMacroExpander + sync::Sync + sync::Send>, Option<Span>, bool),
+    IdentTT {
+        expander: Box<dyn IdentMacroExpander + sync::Sync + sync::Send>,
+        span: Option<Span>,
+        allow_internal_unstable: Option<Lrc<[Symbol]>>,
+    },
 
     /// An attribute-like procedural macro. TokenStream -> TokenStream.
     /// The input is the annotated item.
@@ -682,7 +688,7 @@ impl SyntaxExtension {
         match *self {
             SyntaxExtension::DeclMacro { .. } |
             SyntaxExtension::NormalTT { .. } |
-            SyntaxExtension::IdentTT(..) |
+            SyntaxExtension::IdentTT { .. } |
             SyntaxExtension::ProcMacro { .. } =>
                 MacroKind::Bang,
             SyntaxExtension::NonMacroAttr { .. } |
@@ -716,7 +722,7 @@ impl SyntaxExtension {
             SyntaxExtension::ProcMacroDerive(.., edition) => edition,
             // Unstable legacy stuff
             SyntaxExtension::NonMacroAttr { .. } |
-            SyntaxExtension::IdentTT(..) |
+            SyntaxExtension::IdentTT { .. } |
             SyntaxExtension::MultiDecorator(..) |
             SyntaxExtension::MultiModifier(..) |
             SyntaxExtension::BuiltinDerive(..) => hygiene::default_edition(),
diff --git a/src/libsyntax/ext/derive.rs b/src/libsyntax/ext/derive.rs
index 50cec9e7908..6df369133d0 100644
--- a/src/libsyntax/ext/derive.rs
+++ b/src/libsyntax/ext/derive.rs
@@ -58,7 +58,10 @@ pub fn add_derived_markers<T>(cx: &mut ExtCtxt<'_>, span: Span, traits: &[ast::P
         call_site: span,
         def_site: None,
         format: ExpnFormat::MacroAttribute(Symbol::intern(&pretty_name)),
-        allow_internal_unstable: true,
+        allow_internal_unstable: Some(vec![
+            Symbol::intern("rustc_attrs"),
+            Symbol::intern("structural_match"),
+        ].into()),
         allow_internal_unsafe: false,
         local_inner_macros: false,
         edition: hygiene::default_edition(),
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index 89d59478a5d..60359531b7f 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -558,7 +558,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             call_site: attr.span,
             def_site: None,
             format: MacroAttribute(Symbol::intern(&attr.path.to_string())),
-            allow_internal_unstable: false,
+            allow_internal_unstable: None,
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: ext.edition(),
@@ -725,7 +725,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 // don't stability-check macros in the same crate
                 // (the only time this is null is for syntax extensions registered as macros)
                 if def_site_span.map_or(false, |def_span| !crate_span.contains(def_span))
-                    && !span.allows_unstable() && this.cx.ecfg.features.map_or(true, |feats| {
+                    && !span.allows_unstable(&feature.as_str())
+                    && this.cx.ecfg.features.map_or(true, |feats| {
                     // macro features will count as lib features
                     !feats.declared_lib_features.iter().any(|&(feat, _)| feat == feature)
                 }) {
@@ -757,7 +758,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         let opt_expanded = match *ext {
             DeclMacro { ref expander, def_info, edition, .. } => {
                 if let Err(dummy_span) = validate_and_set_expn_info(self, def_info.map(|(_, s)| s),
-                                                                    false, false, false, None,
+                                                                    None, false, false, None,
                                                                     edition) {
                     dummy_span
                 } else {
@@ -768,14 +769,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             NormalTT {
                 ref expander,
                 def_info,
-                allow_internal_unstable,
+                ref allow_internal_unstable,
                 allow_internal_unsafe,
                 local_inner_macros,
                 unstable_feature,
                 edition,
             } => {
                 if let Err(dummy_span) = validate_and_set_expn_info(self, def_info.map(|(_, s)| s),
-                                                                    allow_internal_unstable,
+                                                                    allow_internal_unstable.clone(),
                                                                     allow_internal_unsafe,
                                                                     local_inner_macros,
                                                                     unstable_feature,
@@ -791,7 +792,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 }
             }
 
-            IdentTT(ref expander, tt_span, allow_internal_unstable) => {
+            IdentTT { ref expander, span: tt_span, ref allow_internal_unstable } => {
                 if ident.name == keywords::Invalid.name() {
                     self.cx.span_err(path.span,
                                     &format!("macro {}! expects an ident argument", path));
@@ -802,7 +803,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         call_site: span,
                         def_site: tt_span,
                         format: macro_bang_format(path),
-                        allow_internal_unstable,
+                        allow_internal_unstable: allow_internal_unstable.clone(),
                         allow_internal_unsafe: false,
                         local_inner_macros: false,
                         edition: hygiene::default_edition(),
@@ -827,7 +828,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 kind.dummy(span)
             }
 
-            SyntaxExtension::ProcMacro { ref expander, allow_internal_unstable, edition } => {
+            SyntaxExtension::ProcMacro { ref expander, ref allow_internal_unstable, edition } => {
                 if ident.name != keywords::Invalid.name() {
                     let msg =
                         format!("macro {}! expects no ident argument, given '{}'", path, ident);
@@ -843,7 +844,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         def_site: None,
                         format: macro_bang_format(path),
                         // FIXME probably want to follow macro_rules macros here.
-                        allow_internal_unstable,
+                        allow_internal_unstable: allow_internal_unstable.clone(),
                         allow_internal_unsafe: false,
                         local_inner_macros: false,
                         edition,
@@ -918,7 +919,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             call_site: span,
             def_site: None,
             format: MacroAttribute(pretty_name),
-            allow_internal_unstable: false,
+            allow_internal_unstable: None,
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: ext.edition(),
@@ -937,7 +938,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 Some(invoc.fragment_kind.expect_from_annotatables(items))
             }
             BuiltinDerive(func) => {
-                expn_info.allow_internal_unstable = true;
+                expn_info.allow_internal_unstable = Some(vec![
+                    Symbol::intern("rustc_attrs"),
+                    Symbol::intern("derive_clone_copy"),
+                    Symbol::intern("derive_eq"),
+                    Symbol::intern("libstd_sys_internals"), // RustcDeserialize and RustcSerialize
+                ].into());
                 invoc.expansion_data.mark.set_expn_info(expn_info);
                 let span = span.with_ctxt(self.cx.backtrace());
                 let mut items = Vec::new();
diff --git a/src/libsyntax/ext/source_util.rs b/src/libsyntax/ext/source_util.rs
index 31a134b856d..549de1628eb 100644
--- a/src/libsyntax/ext/source_util.rs
+++ b/src/libsyntax/ext/source_util.rs
@@ -44,7 +44,7 @@ pub fn expand_column(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTr
 /* __rust_unstable_column!(): expands to the current column number */
 pub fn expand_column_gated(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                   -> Box<dyn base::MacResult+'static> {
-    if sp.allows_unstable() {
+    if sp.allows_unstable("__rust_unstable_column") {
         expand_column(cx, sp, tts)
     } else {
         cx.span_fatal(sp, "the __rust_unstable_column macro is unstable");
diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs
index 33ea675f9d1..cc5531c4010 100644
--- a/src/libsyntax/ext/tt/macro_rules.rs
+++ b/src/libsyntax/ext/tt/macro_rules.rs
@@ -376,7 +376,24 @@ pub fn compile(
     });
 
     if body.legacy {
-        let allow_internal_unstable = attr::contains_name(&def.attrs, "allow_internal_unstable");
+        let allow_internal_unstable = attr::find_by_name(&def.attrs, "allow_internal_unstable")
+            .map(|attr| attr
+                .meta_item_list()
+                .map(|list| list.iter()
+                    .map(|it| it.name().unwrap_or_else(|| sess.span_diagnostic.span_bug(
+                        it.span, "allow internal unstable expects feature names",
+                    )))
+                    .collect::<Vec<Symbol>>().into()
+                )
+                .unwrap_or_else(|| {
+                    sess.span_diagnostic.span_warn(
+                        attr.span, "allow_internal_unstable expects list of feature names. In the \
+                        future this will become a hard error. Please use `allow_internal_unstable(\
+                        foo, bar)` to only allow the `foo` and `bar` features",
+                    );
+                    vec![Symbol::intern("allow_internal_unstable_backcompat_hack")].into()
+                })
+            );
         let allow_internal_unsafe = attr::contains_name(&def.attrs, "allow_internal_unsafe");
         let mut local_inner_macros = false;
         if let Some(macro_export) = attr::find_by_name(&def.attrs, "macro_export") {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 0853b4399d2..4d24c7bfb40 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -1091,7 +1091,8 @@ pub const BUILTIN_ATTRIBUTES: &[(&str, AttributeType, AttributeTemplate, Attribu
                                               stable",
                                              cfg_fn!(profiler_runtime))),
 
-    ("allow_internal_unstable", Normal, template!(Word), Gated(Stability::Unstable,
+    ("allow_internal_unstable", Normal, template!(Word, List: "feat1, feat2, ..."),
+                                              Gated(Stability::Unstable,
                                               "allow_internal_unstable",
                                               EXPLAIN_ALLOW_INTERNAL_UNSTABLE,
                                               cfg_fn!(allow_internal_unstable))),
@@ -1199,7 +1200,7 @@ pub const BUILTIN_ATTRIBUTES: &[(&str, AttributeType, AttributeTemplate, Attribu
     ("proc_macro", Normal, template!(Word), Ungated),
 
     ("rustc_proc_macro_decls", Normal, template!(Word), Gated(Stability::Unstable,
-                                             "rustc_proc_macro_decls",
+                                             "rustc_attrs",
                                              "used internally by rustc",
                                              cfg_fn!(rustc_attrs))),
 
@@ -1284,7 +1285,7 @@ impl GatedCfg {
 
     pub fn check_and_emit(&self, sess: &ParseSess, features: &Features) {
         let (cfg, feature, has_feature) = GATED_CFGS[self.index];
-        if !has_feature(features) && !self.span.allows_unstable() {
+        if !has_feature(features) && !self.span.allows_unstable(feature) {
             let explain = format!("`cfg({})` is experimental and subject to change", cfg);
             emit_feature_err(sess, feature, self.span, GateIssue::Language, &explain);
         }
@@ -1303,7 +1304,7 @@ macro_rules! gate_feature_fn {
              name, explain, level) = ($cx, $has_feature, $span, $name, $explain, $level);
         let has_feature: bool = has_feature(&$cx.features);
         debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", name, span, has_feature);
-        if !has_feature && !span.allows_unstable() {
+        if !has_feature && !span.allows_unstable($name) {
             leveled_feature_err(cx.parse_sess, name, span, GateIssue::Language, explain, level)
                 .emit();
         }
@@ -1328,7 +1329,11 @@ impl<'a> Context<'a> {
         for &(n, ty, _template, ref gateage) in BUILTIN_ATTRIBUTES {
             if name == n {
                 if let Gated(_, name, desc, ref has_feature) = *gateage {
-                    gate_feature_fn!(self, has_feature, attr.span, name, desc, GateStrength::Hard);
+                    if !attr.span.allows_unstable(name) {
+                        gate_feature_fn!(
+                            self, has_feature, attr.span, name, desc, GateStrength::Hard
+                        );
+                    }
                 } else if name == "doc" {
                     if let Some(content) = attr.meta_item_list() {
                         if content.iter().any(|c| c.check_name("include")) {
@@ -1493,13 +1498,13 @@ struct PostExpansionVisitor<'a> {
 macro_rules! gate_feature_post {
     ($cx: expr, $feature: ident, $span: expr, $explain: expr) => {{
         let (cx, span) = ($cx, $span);
-        if !span.allows_unstable() {
+        if !span.allows_unstable(stringify!($feature)) {
             gate_feature!(cx.context, $feature, span, $explain)
         }
     }};
     ($cx: expr, $feature: ident, $span: expr, $explain: expr, $level: expr) => {{
         let (cx, span) = ($cx, $span);
-        if !span.allows_unstable() {
+        if !span.allows_unstable(stringify!($feature)) {
             gate_feature!(cx.context, $feature, span, $explain, $level)
         }
     }}
@@ -1610,10 +1615,8 @@ impl<'a> PostExpansionVisitor<'a> {
 
 impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
     fn visit_attribute(&mut self, attr: &ast::Attribute) {
-        if !attr.span.allows_unstable() {
-            // check for gated attributes
-            self.context.check_attribute(attr, false);
-        }
+        // check for gated attributes
+        self.context.check_attribute(attr, false);
 
         if attr.check_name("doc") {
             if let Some(content) = attr.meta_item_list() {
diff --git a/src/libsyntax/std_inject.rs b/src/libsyntax/std_inject.rs
index 5b904fa86ad..b9758bd655c 100644
--- a/src/libsyntax/std_inject.rs
+++ b/src/libsyntax/std_inject.rs
@@ -20,7 +20,9 @@ fn ignored_span(sp: Span) -> Span {
         call_site: DUMMY_SP,
         def_site: None,
         format: MacroAttribute(Symbol::intern("std_inject")),
-        allow_internal_unstable: true,
+        allow_internal_unstable: Some(vec![
+            Symbol::intern("prelude_import"),
+        ].into()),
         allow_internal_unsafe: false,
         local_inner_macros: false,
         edition: hygiene::default_edition(),
diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs
index 703c4f2db34..f45bf034ba2 100644
--- a/src/libsyntax/test.rs
+++ b/src/libsyntax/test.rs
@@ -285,7 +285,11 @@ fn generate_test_harness(sess: &ParseSess,
         call_site: DUMMY_SP,
         def_site: None,
         format: MacroAttribute(Symbol::intern("test_case")),
-        allow_internal_unstable: true,
+        allow_internal_unstable: Some(vec![
+            Symbol::intern("main"),
+            Symbol::intern("test"),
+            Symbol::intern("rustc_attrs"),
+        ].into()),
         allow_internal_unsafe: false,
         local_inner_macros: false,
         edition: hygiene::default_edition(),
diff --git a/src/libsyntax_ext/deriving/mod.rs b/src/libsyntax_ext/deriving/mod.rs
index 2c8a996cdb0..fff54814a38 100644
--- a/src/libsyntax_ext/deriving/mod.rs
+++ b/src/libsyntax_ext/deriving/mod.rs
@@ -136,11 +136,16 @@ fn call_intrinsic(cx: &ExtCtxt<'_>,
                   intrinsic: &str,
                   args: Vec<P<ast::Expr>>)
                   -> P<ast::Expr> {
-    if cx.current_expansion.mark.expn_info().unwrap().allow_internal_unstable {
+    let intrinsic_allowed_via_allow_internal_unstable = cx
+        .current_expansion.mark.expn_info().unwrap()
+        .allow_internal_unstable.map_or(false, |features| features.iter().any(|&s|
+            s == "core_intrinsics"
+        ));
+    if intrinsic_allowed_via_allow_internal_unstable {
         span = span.with_ctxt(cx.backtrace());
     } else { // Avoid instability errors with user defined curstom derives, cc #36316
         let mut info = cx.current_expansion.mark.expn_info().unwrap();
-        info.allow_internal_unstable = true;
+        info.allow_internal_unstable = Some(vec![Symbol::intern("core_intrinsics")].into());
         let mark = Mark::fresh(Mark::root());
         mark.set_expn_info(info);
         span = span.with_ctxt(SyntaxContext::empty().apply_mark(mark));
diff --git a/src/libsyntax_ext/format.rs b/src/libsyntax_ext/format.rs
index 6bb7ee1d5dd..1b17fc0d040 100644
--- a/src/libsyntax_ext/format.rs
+++ b/src/libsyntax_ext/format.rs
@@ -711,7 +711,7 @@ pub fn expand_format_args_nl<'cx>(
     //if !ecx.ecfg.enable_allow_internal_unstable() {
 
     // For some reason, the only one that actually works for `println` is the first check
-    if !sp.allows_unstable()   // the enclosing span is marked as `#[allow_insternal_unsable]`
+    if !sp.allows_unstable("format_args_nl") // the span is marked as `#[allow_insternal_unsable]`
         && !ecx.ecfg.enable_allow_internal_unstable()  // NOTE: when is this enabled?
         && !ecx.ecfg.enable_format_args_nl()  // enabled using `#[feature(format_args_nl]`
     {
diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs
index 7d7fd030859..05c8084a51e 100644
--- a/src/libsyntax_ext/lib.rs
+++ b/src/libsyntax_ext/lib.rs
@@ -61,7 +61,7 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
                      NormalTT {
                         expander: Box::new($f as MacroExpanderFn),
                         def_info: None,
-                        allow_internal_unstable: false,
+                        allow_internal_unstable: None,
                         allow_internal_unsafe: false,
                         local_inner_macros: false,
                         unstable_feature: None,
@@ -104,7 +104,9 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
              NormalTT {
                 expander: Box::new(format::expand_format_args),
                 def_info: None,
-                allow_internal_unstable: true,
+                allow_internal_unstable: Some(vec![
+                    Symbol::intern("fmt_internals"),
+                ].into()),
                 allow_internal_unsafe: false,
                 local_inner_macros: false,
                 unstable_feature: None,
@@ -114,7 +116,9 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
              NormalTT {
                  expander: Box::new(format::expand_format_args_nl),
                  def_info: None,
-                 allow_internal_unstable: true,
+                 allow_internal_unstable: Some(vec![
+                     Symbol::intern("fmt_internals"),
+                 ].into()),
                  allow_internal_unsafe: false,
                  local_inner_macros: false,
                  unstable_feature: None,
diff --git a/src/libsyntax_ext/proc_macro_decls.rs b/src/libsyntax_ext/proc_macro_decls.rs
index 24d09514520..5730081ce01 100644
--- a/src/libsyntax_ext/proc_macro_decls.rs
+++ b/src/libsyntax_ext/proc_macro_decls.rs
@@ -333,7 +333,10 @@ fn mk_decls(
         call_site: DUMMY_SP,
         def_site: None,
         format: MacroAttribute(Symbol::intern("proc_macro")),
-        allow_internal_unstable: true,
+        allow_internal_unstable: Some(vec![
+            Symbol::intern("rustc_attrs"),
+            Symbol::intern("proc_macro_internals"),
+        ].into()),
         allow_internal_unsafe: false,
         local_inner_macros: false,
         edition: hygiene::default_edition(),
diff --git a/src/libsyntax_ext/test.rs b/src/libsyntax_ext/test.rs
index 832bebb6113..37186246548 100644
--- a/src/libsyntax_ext/test.rs
+++ b/src/libsyntax_ext/test.rs
@@ -66,7 +66,10 @@ pub fn expand_test_or_bench(
             call_site: DUMMY_SP,
             def_site: None,
             format: MacroAttribute(Symbol::intern("test")),
-            allow_internal_unstable: true,
+            allow_internal_unstable: Some(vec![
+                Symbol::intern("rustc_attrs"),
+                Symbol::intern("test"),
+            ].into()),
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: hygiene::default_edition(),
diff --git a/src/libsyntax_ext/test_case.rs b/src/libsyntax_ext/test_case.rs
index 63417b702d5..1ed1ab0a07b 100644
--- a/src/libsyntax_ext/test_case.rs
+++ b/src/libsyntax_ext/test_case.rs
@@ -41,7 +41,10 @@ pub fn expand(
             call_site: DUMMY_SP,
             def_site: None,
             format: MacroAttribute(Symbol::intern("test_case")),
-            allow_internal_unstable: true,
+            allow_internal_unstable: Some(vec![
+                Symbol::intern("test"),
+                Symbol::intern("rustc_attrs"),
+            ].into()),
             allow_internal_unsafe: false,
             local_inner_macros: false,
             edition: hygiene::default_edition(),
diff --git a/src/libsyntax_pos/hygiene.rs b/src/libsyntax_pos/hygiene.rs
index 0c645fc678c..d5c0a2ca85f 100644
--- a/src/libsyntax_pos/hygiene.rs
+++ b/src/libsyntax_pos/hygiene.rs
@@ -12,6 +12,7 @@ use crate::symbol::{keywords, Symbol};
 
 use serialize::{Encodable, Decodable, Encoder, Decoder};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::sync::Lrc;
 use std::{fmt, mem};
 
 /// A SyntaxContext represents a chain of macro expansions (represented by marks).
@@ -550,10 +551,10 @@ pub struct ExpnInfo {
     pub def_site: Option<Span>,
     /// The format with which the macro was invoked.
     pub format: ExpnFormat,
-    /// Whether the macro is allowed to use #[unstable]/feature-gated
-    /// features internally without forcing the whole crate to opt-in
+    /// List of #[unstable]/feature-gated features that the macro is allowed to use
+    /// internally without forcing the whole crate to opt-in
     /// to them.
-    pub allow_internal_unstable: bool,
+    pub allow_internal_unstable: Option<Lrc<[Symbol]>>,
     /// Whether the macro is allowed to use `unsafe` internally
     /// even if the user crate has `#![forbid(unsafe_code)]`.
     pub allow_internal_unsafe: bool,
diff --git a/src/libsyntax_pos/lib.rs b/src/libsyntax_pos/lib.rs
index dbb4f8f8159..042005ea538 100644
--- a/src/libsyntax_pos/lib.rs
+++ b/src/libsyntax_pos/lib.rs
@@ -386,9 +386,13 @@ impl Span {
     /// Check if a span is "internal" to a macro in which `#[unstable]`
     /// items can be used (that is, a macro marked with
     /// `#[allow_internal_unstable]`).
-    pub fn allows_unstable(&self) -> bool {
+    pub fn allows_unstable(&self, feature: &str) -> bool {
         match self.ctxt().outer().expn_info() {
-            Some(info) => info.allow_internal_unstable,
+            Some(info) => info
+                .allow_internal_unstable
+                .map_or(false, |features| features.iter().any(|&f|
+                    f == feature || f == "allow_internal_unstable_backcompat_hack"
+                )),
             None => false,
         }
     }
diff --git a/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs b/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs
index eab612c5dfb..309acb25184 100644
--- a/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs
@@ -43,7 +43,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
         NormalTT {
             expander: Box::new(Expander { args: args, }),
             def_info: None,
-            allow_internal_unstable: false,
+            allow_internal_unstable: None,
             allow_internal_unsafe: false,
             local_inner_macros: false,
             unstable_feature: None,
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.rs b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.rs
index cf320b2747c..ee48f951629 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.rs
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.rs
@@ -5,7 +5,7 @@
 macro_rules! bar {
     () => {
         // more layers don't help:
-        #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+        #[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
         macro_rules! baz {
             () => {}
         }
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.stderr b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.stderr
index 7cdf743b279..802c74239d7 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.stderr
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-nested-macro.stderr
@@ -1,8 +1,8 @@
 error[E0658]: allow_internal_unstable side-steps feature gating and stability checks
   --> $DIR/feature-gate-allow-internal-unstable-nested-macro.rs:8:9
    |
-LL |         #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         #[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ...
 LL | bar!();
    | ------- in this macro invocation
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.rs b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.rs
index c9ea6c338b5..ede969097d5 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.rs
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.rs
@@ -1,7 +1,7 @@
 // checks that this attribute is caught on non-macro items.
 // this needs a different test since this is done after expansion
 
-#[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+#[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
 struct S;
 
 fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.stderr b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.stderr
index 485c00ad42b..d619f1e3239 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.stderr
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable-struct.stderr
@@ -1,8 +1,8 @@
 error[E0658]: allow_internal_unstable side-steps feature gating and stability checks
   --> $DIR/feature-gate-allow-internal-unstable-struct.rs:4:1
    |
-LL | #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: add #![feature(allow_internal_unstable)] to the crate attributes to enable
 
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.rs b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.rs
index bea60fc012e..0a1b6acd9bf 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.rs
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.rs
@@ -1,6 +1,6 @@
 #![allow(unused_macros)]
 
-#[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+#[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
 macro_rules! foo {
     () => {}
 }
diff --git a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.stderr b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.stderr
index 0533d071de3..aa4f6648c4f 100644
--- a/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.stderr
+++ b/src/test/ui/feature-gates/feature-gate-allow-internal-unstable.stderr
@@ -1,8 +1,8 @@
 error[E0658]: allow_internal_unstable side-steps feature gating and stability checks
   --> $DIR/feature-gate-allow-internal-unstable.rs:3:1
    |
-LL | #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[allow_internal_unstable()] //~ ERROR allow_internal_unstable side-steps
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: add #![feature(allow_internal_unstable)] to the crate attributes to enable
 
diff --git a/src/test/ui/internal/auxiliary/internal_unstable.rs b/src/test/ui/internal/auxiliary/internal_unstable.rs
index 7cf54c3e6a6..7c79dcb7522 100644
--- a/src/test/ui/internal/auxiliary/internal_unstable.rs
+++ b/src/test/ui/internal/auxiliary/internal_unstable.rs
@@ -23,14 +23,14 @@ pub struct Bar {
 }
 
 #[stable(feature = "stable", since = "1.0.0")]
-#[allow_internal_unstable]
+#[allow_internal_unstable(function)]
 #[macro_export]
 macro_rules! call_unstable_allow {
     () => { $crate::unstable() }
 }
 
 #[stable(feature = "stable", since = "1.0.0")]
-#[allow_internal_unstable]
+#[allow_internal_unstable(struct_field)]
 #[macro_export]
 macro_rules! construct_unstable_allow {
     ($e: expr) => {
@@ -39,21 +39,21 @@ macro_rules! construct_unstable_allow {
 }
 
 #[stable(feature = "stable", since = "1.0.0")]
-#[allow_internal_unstable]
+#[allow_internal_unstable(method)]
 #[macro_export]
 macro_rules! call_method_allow {
     ($e: expr) => { $e.method() }
 }
 
 #[stable(feature = "stable", since = "1.0.0")]
-#[allow_internal_unstable]
+#[allow_internal_unstable(struct_field, struct2_field)]
 #[macro_export]
 macro_rules! access_field_allow {
     ($e: expr) => { $e.x }
 }
 
 #[stable(feature = "stable", since = "1.0.0")]
-#[allow_internal_unstable]
+#[allow_internal_unstable()]
 #[macro_export]
 macro_rules! pass_through_allow {
     ($e: expr) => { $e }
diff --git a/src/test/ui/internal/internal-unstable.rs b/src/test/ui/internal/internal-unstable.rs
index 34fef33bfeb..e09a5d89172 100644
--- a/src/test/ui/internal/internal-unstable.rs
+++ b/src/test/ui/internal/internal-unstable.rs
@@ -13,7 +13,7 @@ macro_rules! foo {
     }}
 }
 
-#[allow_internal_unstable]
+#[allow_internal_unstable(function)]
 macro_rules! bar {
     ($e: expr) => {{
         foo!($e,
diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs
index 0f722945c49..ed2218f09d2 100644
--- a/src/tools/tidy/src/pal.rs
+++ b/src/tools/tidy/src/pal.rs
@@ -58,6 +58,7 @@ const EXCEPTION_PATHS: &[&str] = &[
     "src/libstd/sys_common/net.rs",
     "src/libterm", // Not sure how to make this crate portable, but test crate needs it.
     "src/libtest", // Probably should defer to unstable `std::sys` APIs.
+    "src/libstd/sync/mpsc", // some tests are only run on non-emscripten
 
     // std testing crates, okay for now at least
     "src/libcore/tests",