diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index de1a740e0bba4..bda905f3555f5 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -512,19 +512,13 @@ pub fn phase_2_configure_and_expand(sess: &Session,
         middle::recursion_limit::update_recursion_limit(sess, &krate);
     });
 
-    time(time_passes, "gated macro checking", || {
-        sess.track_errors(|| {
-            let features =
-              syntax::feature_gate::check_crate_macros(sess.codemap(),
-                                                       &sess.parse_sess.span_diagnostic,
-                                                       &krate);
-
-            // these need to be set "early" so that expansion sees `quote` if enabled.
-            *sess.features.borrow_mut() = features;
-        })
+    // these need to be set "early" so that expansion sees `quote` if enabled.
+    sess.track_errors(|| {
+        *sess.features.borrow_mut() =
+            syntax::feature_gate::get_features(&sess.parse_sess.span_diagnostic,
+                                               &krate);
     })?;
 
-
     krate = time(time_passes, "crate injection", || {
         syntax::std_inject::maybe_inject_crates_ref(krate, sess.opts.alt_std_name.clone())
     });
diff --git a/src/librustc_plugin/load.rs b/src/librustc_plugin/load.rs
index ac40215bbb1d0..036e46c380398 100644
--- a/src/librustc_plugin/load.rs
+++ b/src/librustc_plugin/load.rs
@@ -51,27 +51,32 @@ pub fn load_plugins(sess: &Session,
                     addl_plugins: Option<Vec<String>>) -> Vec<PluginRegistrar> {
     let mut loader = PluginLoader::new(sess, cstore, crate_name);
 
-    for attr in &krate.attrs {
-        if !attr.check_name("plugin") {
-            continue;
-        }
-
-        let plugins = match attr.meta_item_list() {
-            Some(xs) => xs,
-            None => {
-                call_malformed_plugin_attribute(sess, attr.span);
+    // do not report any error now. since crate attributes are
+    // not touched by expansion, every use of plugin without
+    // the feature enabled will result in an error later...
+    if sess.features.borrow().plugin {
+        for attr in &krate.attrs {
+            if !attr.check_name("plugin") {
                 continue;
             }
-        };
 
-        for plugin in plugins {
-            if plugin.value_str().is_some() {
-                call_malformed_plugin_attribute(sess, attr.span);
-                continue;
+            let plugins = match attr.meta_item_list() {
+                Some(xs) => xs,
+                None => {
+                    call_malformed_plugin_attribute(sess, attr.span);
+                    continue;
+                }
+            };
+
+            for plugin in plugins {
+                if plugin.value_str().is_some() {
+                    call_malformed_plugin_attribute(sess, attr.span);
+                    continue;
+                }
+
+                let args = plugin.meta_item_list().map(ToOwned::to_owned).unwrap_or_default();
+                loader.load_plugin(plugin.span, &plugin.name(), args);
             }
-
-            let args = plugin.meta_item_list().map(ToOwned::to_owned).unwrap_or_default();
-            loader.load_plugin(plugin.span, &plugin.name(), args);
         }
     }
 
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index cd7b0fcfb0044..3424696fa7fe7 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -35,6 +35,16 @@ use std_inject;
 use std::collections::HashSet;
 use std::env;
 
+// this function is called to detect use of feature-gated or invalid attributes
+// on macro invoations since they will not be detected after macro expansion
+fn check_attributes(attrs: &[ast::Attribute], fld: &MacroExpander) {
+    for attr in attrs.iter() {
+        feature_gate::check_attribute(&attr, &fld.cx.parse_sess.span_diagnostic,
+                                      &fld.cx.parse_sess.codemap(),
+                                      &fld.cx.ecfg.features.unwrap());
+    }
+}
+
 pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
     let expr_span = e.span;
     return e.and_then(|ast::Expr {id, node, span, attrs}| match node {
@@ -42,6 +52,9 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
         // expr_mac should really be expr_ext or something; it's the
         // entry-point for all syntax extensions.
         ast::ExprKind::Mac(mac) => {
+            if let Some(ref attrs) = attrs {
+                check_attributes(attrs, fld);
+            }
 
             // Assert that we drop any macro attributes on the floor here
             drop(attrs);
@@ -70,10 +83,12 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
 
         ast::ExprKind::InPlace(placer, value_expr) => {
             // Ensure feature-gate is enabled
-            feature_gate::check_for_placement_in(
-                fld.cx.ecfg.features,
-                &fld.cx.parse_sess.span_diagnostic,
-                expr_span);
+            if !fld.cx.ecfg.features.unwrap().placement_in_syntax {
+                feature_gate::emit_feature_err(
+                    &fld.cx.parse_sess.span_diagnostic, "placement_in_syntax", expr_span,
+                    feature_gate::GateIssue::Language, feature_gate::EXPLAIN_PLACEMENT_IN
+                );
+            }
 
             let placer = fld.fold_expr(placer);
             let value_expr = fld.fold_expr(value_expr);
@@ -367,6 +382,8 @@ pub fn expand_item_mac(it: P<ast::Item>,
         _ => fld.cx.span_bug(it.span, "invalid item macro invocation")
     });
 
+    check_attributes(&attrs, fld);
+
     let fm = fresh_mark();
     let items = {
         let expanded = match fld.cx.syntax_env.find(extname) {
@@ -441,18 +458,6 @@ pub fn expand_item_mac(it: P<ast::Item>,
                     let allow_internal_unstable = attr::contains_name(&attrs,
                                                                       "allow_internal_unstable");
 
-                    // ensure any #[allow_internal_unstable]s are
-                    // detected (including nested macro definitions
-                    // etc.)
-                    if allow_internal_unstable && !fld.cx.ecfg.enable_allow_internal_unstable() {
-                        feature_gate::emit_feature_err(
-                            &fld.cx.parse_sess.span_diagnostic,
-                            "allow_internal_unstable",
-                            span,
-                            feature_gate::GateIssue::Language,
-                            feature_gate::EXPLAIN_ALLOW_INTERNAL_UNSTABLE)
-                    }
-
                     let export = attr::contains_name(&attrs, "macro_export");
                     let def = ast::MacroDef {
                         ident: ident,
@@ -516,6 +521,10 @@ fn expand_stmt(stmt: Stmt, fld: &mut MacroExpander) -> SmallVector<Stmt> {
         _ => return expand_non_macro_stmt(stmt, fld)
     };
 
+    if let Some(ref attrs) = attrs {
+        check_attributes(attrs, fld);
+    }
+
     // Assert that we drop any macro attributes on the floor here
     drop(attrs);
 
@@ -1063,7 +1072,7 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander)
             attrs: ii.attrs,
             vis: ii.vis,
             defaultness: ii.defaultness,
-            node: match ii.node  {
+            node: match ii.node {
                 ast::ImplItemKind::Method(sig, body) => {
                     let (sig, body) = expand_and_rename_method(sig, body, fld);
                     ast::ImplItemKind::Method(sig, body)
@@ -1072,13 +1081,11 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander)
             },
             span: fld.new_span(ii.span)
         }),
-        ast::ImplItemKind::Macro(_) => {
-            let (span, mac) = match ii.node {
-                ast::ImplItemKind::Macro(mac) => (ii.span, mac),
-                _ => unreachable!()
-            };
+        ast::ImplItemKind::Macro(mac) => {
+            check_attributes(&ii.attrs, fld);
+
             let maybe_new_items =
-                expand_mac_invoc(mac, span,
+                expand_mac_invoc(mac, ii.span,
                                  |r| r.make_impl_items(),
                                  |meths, mark| meths.move_map(|m| mark_impl_item(m, mark)),
                                  fld);
@@ -1345,14 +1352,14 @@ impl<'feat> ExpansionConfig<'feat> {
     }
 
     feature_tests! {
-        fn enable_quotes = allow_quote,
-        fn enable_asm = allow_asm,
-        fn enable_log_syntax = allow_log_syntax,
-        fn enable_concat_idents = allow_concat_idents,
-        fn enable_trace_macros = allow_trace_macros,
+        fn enable_quotes = quote,
+        fn enable_asm = asm,
+        fn enable_log_syntax = log_syntax,
+        fn enable_concat_idents = concat_idents,
+        fn enable_trace_macros = trace_macros,
         fn enable_allow_internal_unstable = allow_internal_unstable,
-        fn enable_custom_derive = allow_custom_derive,
-        fn enable_pushpop_unsafe = allow_pushpop_unsafe,
+        fn enable_custom_derive = custom_derive,
+        fn enable_pushpop_unsafe = pushpop_unsafe,
     }
 }
 
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index d8352430eb94e..c6b398507d453 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -22,7 +22,6 @@
 //! gate usage is added, *do not remove it again* even once the feature
 //! becomes stable.
 
-use self::Status::*;
 use self::AttributeType::*;
 use self::AttributeGate::*;
 
@@ -40,125 +39,146 @@ use parse::token::InternedString;
 use std::ascii::AsciiExt;
 use std::cmp;
 
+macro_rules! setter {
+    ($field: ident) => {{
+        fn f(features: &mut Features) -> &mut bool {
+            &mut features.$field
+        }
+        f as fn(&mut Features) -> &mut bool
+    }}
+}
+
+macro_rules! declare_features {
+    ($((active, $feature: ident, $ver: expr, $issue: expr)),+) => {
+        /// Represents active features that are currently being implemented or
+        /// currently being considered for addition/removal.
+        const ACTIVE_FEATURES: &'static [(&'static str, &'static str,
+                                          Option<u32>, fn(&mut Features) -> &mut bool)] = &[
+            $((stringify!($feature), $ver, $issue, setter!($feature))),+
+        ];
+
+        /// A set of features to be used by later passes.
+        pub struct Features {
+            /// spans of #![feature] attrs for stable language features. for error reporting
+            pub declared_stable_lang_features: Vec<Span>,
+            /// #![feature] attrs for non-language (library) features
+            pub declared_lib_features: Vec<(InternedString, Span)>,
+            $(pub $feature: bool),+
+        }
+
+        impl Features {
+            pub fn new() -> Features {
+                Features {
+                    declared_stable_lang_features: Vec::new(),
+                    declared_lib_features: Vec::new(),
+                    $($feature: false),+
+                }
+            }
+        }
+    };
+
+    ($((removed, $feature: ident, $ver: expr, $issue: expr)),+) => {
+        /// Represents features which has since been removed (it was once Active)
+        const REMOVED_FEATURES: &'static [(&'static str, &'static str, Option<u32>)] = &[
+            $((stringify!($feature), $ver, $issue)),+
+        ];
+    };
+
+    ($((accepted, $feature: ident, $ver: expr, $issue: expr)),+) => {
+        /// Those language feature has since been Accepted (it was once Active)
+        const ACCEPTED_FEATURES: &'static [(&'static str, &'static str, Option<u32>)] = &[
+            $((stringify!($feature), $ver, $issue)),+
+        ];
+    }
+}
+
 // If you change this list without updating src/doc/reference.md, @cmr will be sad
 // Don't ever remove anything from this list; set them to 'Removed'.
 // The version numbers here correspond to the version in which the current status
 // was set. This is most important for knowing when a particular feature became
 // stable (active).
-// NB: The tidy tool parses this information directly out of the source so take
-// care when modifying it.
-const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option<u32>, Status)] = &[
-    ("globs", "1.0.0", None, Accepted),
-    ("macro_rules", "1.0.0", None, Accepted),
-    ("struct_variant", "1.0.0", None, Accepted),
-    ("asm", "1.0.0", Some(29722), Active),
-    ("managed_boxes", "1.0.0", None, Removed),
-    ("non_ascii_idents", "1.0.0", Some(28979), Active),
-    ("thread_local", "1.0.0", Some(29594), Active),
-    ("link_args", "1.0.0", Some(29596), Active),
-    ("plugin_registrar", "1.0.0", Some(29597), Active),
-    ("log_syntax", "1.0.0", Some(29598), Active),
-    ("trace_macros", "1.0.0", Some(29598), Active),
-    ("concat_idents", "1.0.0", Some(29599), Active),
+// NB: The featureck.py script parses this information directly out of the source
+// so take care when modifying it.
+
+declare_features! (
+    (active, asm, "1.0.0", Some(29722)),
+    (active, concat_idents, "1.0.0", Some(29599)),
+    (active, link_args, "1.0.0", Some(29596)),
+    (active, log_syntax, "1.0.0", Some(29598)),
+    (active, non_ascii_idents, "1.0.0", Some(28979)),
+    (active, plugin_registrar, "1.0.0", Some(29597)),
+    (active, thread_local, "1.0.0", Some(29594)),
+    (active, trace_macros, "1.0.0", Some(29598)),
 
     // rustc internal, for now:
-    ("intrinsics", "1.0.0", None, Active),
-    ("lang_items", "1.0.0", None, Active),
+    (active, intrinsics, "1.0.0", None),
+    (active, lang_items, "1.0.0", None),
 
-    ("simd", "1.0.0", Some(27731), Active),
-    ("default_type_params", "1.0.0", None, Accepted),
-    ("quote", "1.0.0", Some(29601), Active),
-    ("link_llvm_intrinsics", "1.0.0", Some(29602), Active),
-    ("linkage", "1.0.0", Some(29603), Active),
-    ("struct_inherit", "1.0.0", None, Removed),
+    (active, link_llvm_intrinsics, "1.0.0", Some(29602)),
+    (active, linkage, "1.0.0", Some(29603)),
+    (active, quote, "1.0.0", Some(29601)),
+    (active, simd, "1.0.0", Some(27731)),
 
-    ("quad_precision_float", "1.0.0", None, Removed),
 
     // rustc internal
-    ("rustc_diagnostic_macros", "1.0.0", None, Active),
-    ("unboxed_closures", "1.0.0", Some(29625), Active),
-    ("reflect", "1.0.0", Some(27749), Active),
-    ("import_shadowing", "1.0.0", None, Removed),
-    ("advanced_slice_patterns", "1.0.0", Some(23121), Active),
-    ("tuple_indexing", "1.0.0", None, Accepted),
-    ("associated_types", "1.0.0", None, Accepted),
-    ("visible_private_types", "1.0.0", None, Removed),
-    ("slicing_syntax", "1.0.0", None, Accepted),
-    ("box_syntax", "1.0.0", Some(27779), Active),
-    ("placement_in_syntax", "1.0.0", Some(27779), Active),
+    (active, rustc_diagnostic_macros, "1.0.0", None),
+    (active, advanced_slice_patterns, "1.0.0", Some(23121)),
+    (active, box_syntax, "1.0.0", Some(27779)),
+    (active, placement_in_syntax, "1.0.0", Some(27779)),
+    (active, reflect, "1.0.0", Some(27749)),
+    (active, unboxed_closures, "1.0.0", Some(29625)),
 
     // rustc internal.
-    ("pushpop_unsafe", "1.2.0", None, Active),
-
-    ("on_unimplemented", "1.0.0", Some(29628), Active),
-    ("simd_ffi", "1.0.0", Some(27731), Active),
-    ("allocator", "1.0.0", Some(27389), Active),
-    ("needs_allocator", "1.4.0", Some(27389), Active),
-    ("linked_from", "1.3.0", Some(29629), Active),
-
-    ("if_let", "1.0.0", None, Accepted),
-    ("while_let", "1.0.0", None, Accepted),
-
-    ("plugin", "1.0.0", Some(29597), Active),
-    ("start", "1.0.0", Some(29633), Active),
-    ("main", "1.0.0", Some(29634), Active),
-
-    ("fundamental", "1.0.0", Some(29635), Active),
-
-    // A temporary feature gate used to enable parser extensions needed
-    // to bootstrap fix for #5723.
-    ("issue_5723_bootstrap", "1.0.0", None, Accepted),
-
-    ("structural_match", "1.8.0", Some(31434), Active),
-
-    // A way to temporarily opt out of opt in copy. This will *never* be accepted.
-    ("opt_out_copy", "1.0.0", None, Removed),
+    (active, pushpop_unsafe, "1.2.0", None),
+
+    (active, allocator, "1.0.0", Some(27389)),
+    (active, fundamental, "1.0.0", Some(29635)),
+    (active, linked_from, "1.3.0", Some(29629)),
+    (active, main, "1.0.0", Some(29634)),
+    (active, needs_allocator, "1.4.0", Some(27389)),
+    (active, on_unimplemented, "1.0.0", Some(29628)),
+    (active, plugin, "1.0.0", Some(29597)),
+    (active, simd_ffi, "1.0.0", Some(27731)),
+    (active, start, "1.0.0", Some(29633)),
+    (active, structural_match, "1.8.0", Some(31434)),
 
     // OIBIT specific features
-    ("optin_builtin_traits", "1.0.0", Some(13231), Active),
+    (active, optin_builtin_traits, "1.0.0", Some(13231)),
 
     // macro reexport needs more discussion and stabilization
-    ("macro_reexport", "1.0.0", Some(29638), Active),
-
-    // These are used to test this portion of the compiler, they don't actually
-    // mean anything
-    ("test_accepted_feature", "1.0.0", None, Accepted),
-    ("test_removed_feature", "1.0.0", None, Removed),
+    (active, macro_reexport, "1.0.0", Some(29638)),
 
     // Allows use of #[staged_api]
     // rustc internal
-    ("staged_api", "1.0.0", None, Active),
+    (active, staged_api, "1.0.0", None),
 
     // Allows using items which are missing stability attributes
     // rustc internal
-    ("unmarked_api", "1.0.0", None, Active),
-
-    // Allows using #![no_std]
-    ("no_std", "1.0.0", None, Accepted),
+    (active, unmarked_api, "1.0.0", None),
 
     // Allows using #![no_core]
-    ("no_core", "1.3.0", Some(29639), Active),
+    (active, no_core, "1.3.0", Some(29639)),
 
     // Allows using `box` in patterns; RFC 469
-    ("box_patterns", "1.0.0", Some(29641), Active),
+    (active, box_patterns, "1.0.0", Some(29641)),
 
     // Allows using the unsafe_no_drop_flag attribute (unlikely to
     // switch to Accepted; see RFC 320)
-    ("unsafe_no_drop_flag", "1.0.0", None, Active),
+    (active, unsafe_no_drop_flag, "1.0.0", None),
 
     // Allows using the unsafe_destructor_blind_to_params attribute;
     // RFC 1238
-    ("dropck_parametricity", "1.3.0", Some(28498), Active),
+    (active, dropck_parametricity, "1.3.0", Some(28498)),
 
     // Allows the use of custom attributes; RFC 572
-    ("custom_attribute", "1.0.0", Some(29642), Active),
+    (active, custom_attribute, "1.0.0", Some(29642)),
 
     // Allows the use of #[derive(Anything)] as sugar for
     // #[derive_Anything].
-    ("custom_derive", "1.0.0", Some(29644), Active),
+    (active, custom_derive, "1.0.0", Some(29644)),
 
     // Allows the use of rustc_* attributes; RFC 572
-    ("rustc_attrs", "1.0.0", Some(29642), Active),
+    (active, rustc_attrs, "1.0.0", Some(29642)),
 
     // Allows the use of #[allow_internal_unstable]. This is an
     // attribute on macro_rules! and can't use the attribute handling
@@ -166,112 +186,171 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option<u32>, Status
     // macros disappear).
     //
     // rustc internal
-    ("allow_internal_unstable", "1.0.0", None, Active),
+    (active, allow_internal_unstable, "1.0.0", None),
 
     // #23121. Array patterns have some hazards yet.
-    ("slice_patterns", "1.0.0", Some(23121), Active),
-
-    // Allows use of unary negate on unsigned integers, e.g. -e for e: u8
-    ("negate_unsigned", "1.0.0", Some(29645), Removed),
+    (active, slice_patterns, "1.0.0", Some(23121)),
 
     // Allows the definition of associated constants in `trait` or `impl`
     // blocks.
-    ("associated_consts", "1.0.0", Some(29646), Active),
+    (active, associated_consts, "1.0.0", Some(29646)),
 
     // Allows the definition of `const fn` functions.
-    ("const_fn", "1.2.0", Some(24111), Active),
+    (active, const_fn, "1.2.0", Some(24111)),
 
     // Allows indexing into constant arrays.
-    ("const_indexing", "1.4.0", Some(29947), Active),
+    (active, const_indexing, "1.4.0", Some(29947)),
 
     // Allows using #[prelude_import] on glob `use` items.
     //
     // rustc internal
-    ("prelude_import", "1.2.0", None, Active),
+    (active, prelude_import, "1.2.0", None),
 
     // Allows the definition recursive static items.
-    ("static_recursion", "1.3.0", Some(29719), Active),
+    (active, static_recursion, "1.3.0", Some(29719)),
 
     // Allows default type parameters to influence type inference.
-    ("default_type_parameter_fallback", "1.3.0", Some(27336), Active),
+    (active, default_type_parameter_fallback, "1.3.0", Some(27336)),
 
     // Allows associated type defaults
-    ("associated_type_defaults", "1.2.0", Some(29661), Active),
+    (active, associated_type_defaults, "1.2.0", Some(29661)),
 
     // Allows macros to appear in the type position.
-    ("type_macros", "1.3.0", Some(27245), Active),
+    (active, type_macros, "1.3.0", Some(27245)),
 
     // allow `repr(simd)`, and importing the various simd intrinsics
-    ("repr_simd", "1.4.0", Some(27731), Active),
+    (active, repr_simd, "1.4.0", Some(27731)),
 
     // Allows cfg(target_feature = "...").
-    ("cfg_target_feature", "1.4.0", Some(29717), Active),
+    (active, cfg_target_feature, "1.4.0", Some(29717)),
 
     // allow `extern "platform-intrinsic" { ... }`
-    ("platform_intrinsics", "1.4.0", Some(27731), Active),
+    (active, platform_intrinsics, "1.4.0", Some(27731)),
 
     // allow `#[unwind]`
     // rust runtime internal
-    ("unwind_attributes", "1.4.0", None, Active),
+    (active, unwind_attributes, "1.4.0", None),
 
     // allow the use of `#[naked]` on functions.
-    ("naked_functions", "1.9.0", Some(32408), Active),
-
-    // allow empty structs and enum variants with braces
-    ("braced_empty_structs", "1.8.0", Some(29720), Accepted),
-
-    // allow overloading augmented assignment operations like `a += b`
-    ("augmented_assignments", "1.8.0", Some(28235), Accepted),
+    (active, naked_functions, "1.9.0", Some(32408)),
 
     // allow `#[no_debug]`
-    ("no_debug", "1.5.0", Some(29721), Active),
+    (active, no_debug, "1.5.0", Some(29721)),
 
     // allow `#[omit_gdb_pretty_printer_section]`
     // rustc internal.
-    ("omit_gdb_pretty_printer_section", "1.5.0", None, Active),
+    (active, omit_gdb_pretty_printer_section, "1.5.0", None),
 
     // Allows cfg(target_vendor = "...").
-    ("cfg_target_vendor", "1.5.0", Some(29718), Active),
+    (active, cfg_target_vendor, "1.5.0", Some(29718)),
 
     // Allow attributes on expressions and non-item statements
-    ("stmt_expr_attributes", "1.6.0", Some(15701), Active),
-
-    // Allows `#[deprecated]` attribute
-    ("deprecated", "1.9.0", Some(29935), Accepted),
+    (active, stmt_expr_attributes, "1.6.0", Some(15701)),
 
     // allow using type ascription in expressions
-    ("type_ascription", "1.6.0", Some(23416), Active),
+    (active, type_ascription, "1.6.0", Some(23416)),
 
     // Allows cfg(target_thread_local)
-    ("cfg_target_thread_local", "1.7.0", Some(29594), Active),
+    (active, cfg_target_thread_local, "1.7.0", Some(29594)),
 
     // rustc internal
-    ("abi_vectorcall", "1.7.0", None, Active),
+    (active, abi_vectorcall, "1.7.0", None),
 
     // a...b and ...b
-    ("inclusive_range_syntax", "1.7.0", Some(28237), Active),
+    (active, inclusive_range_syntax, "1.7.0", Some(28237)),
 
     // `expr?`
-    ("question_mark", "1.9.0", Some(31436), Active),
+    (active, question_mark, "1.9.0", Some(31436)),
 
     // impl specialization (RFC 1210)
-    ("specialization", "1.7.0", Some(31844), Active),
+    (active, specialization, "1.7.0", Some(31844)),
 
     // pub(restricted) visibilities (RFC 1422)
-    ("pub_restricted", "1.9.0", Some(32409), Active),
-];
+    (active, pub_restricted, "1.9.0", Some(32409))
+);
+
+declare_features! (
+    (removed, import_shadowing, "1.0.0", None),
+    (removed, managed_boxes, "1.0.0", None),
+    // Allows use of unary negate on unsigned integers, e.g. -e for e: u8
+    (removed, negate_unsigned, "1.0.0", Some(29645)),
+    // A way to temporarily opt out of opt in copy. This will *never* be accepted.
+    (removed, opt_out_copy, "1.0.0", None),
+    (removed, quad_precision_float, "1.0.0", None),
+    (removed, struct_inherit, "1.0.0", None),
+    (removed, test_removed_feature, "1.0.0", None),
+    (removed, visible_private_types, "1.0.0", None)
+);
+
+declare_features! (
+    (accepted, associated_types, "1.0.0", None),
+    // allow overloading augmented assignment operations like `a += b`
+    (accepted, augmented_assignments, "1.8.0", Some(28235)),
+    // allow empty structs and enum variants with braces
+    (accepted, braced_empty_structs, "1.8.0", Some(29720)),
+    (accepted, default_type_params, "1.0.0", None),
+    (accepted, globs, "1.0.0", None),
+    (accepted, if_let, "1.0.0", None),
+    // A temporary feature gate used to enable parser extensions needed
+    // to bootstrap fix for #5723.
+    (accepted, issue_5723_bootstrap, "1.0.0", None),
+    (accepted, macro_rules, "1.0.0", None),
+    // Allows using #![no_std]
+    (accepted, no_std, "1.0.0", None),
+    (accepted, slicing_syntax, "1.0.0", None),
+    (accepted, struct_variant, "1.0.0", None),
+    // These are used to test this portion of the compiler, they don't actually
+    // mean anything
+    (accepted, test_accepted_feature, "1.0.0", None),
+    (accepted, tuple_indexing, "1.0.0", None),
+    (accepted, while_let, "1.0.0", None),
+    // Allows `#[deprecated]` attribute
+    (accepted, deprecated, "1.9.0", Some(29935))
+);
+
 // (changing above list without updating src/doc/reference.md makes @cmr sad)
 
-enum Status {
-    /// Represents an active feature that is currently being implemented or
-    /// currently being considered for addition/removal.
-    Active,
+#[derive(PartialEq, Copy, Clone, Debug)]
+pub enum AttributeType {
+    /// Normal, builtin attribute that is consumed
+    /// by the compiler before the unused_attribute check
+    Normal,
 
-    /// Represents a feature which has since been removed (it was once Active)
-    Removed,
+    /// Builtin attribute that may not be consumed by the compiler
+    /// before the unused_attribute check. These attributes
+    /// will be ignored by the unused_attribute lint
+    Whitelisted,
 
-    /// This language feature has since been Accepted (it was once Active)
-    Accepted,
+    /// Builtin attribute that is only allowed at the crate level
+    CrateLevel,
+}
+
+pub enum AttributeGate {
+    /// Is gated by a given feature gate, reason
+    /// and function to check if enabled
+    Gated(&'static str, &'static str, fn(&Features) -> bool),
+
+    /// Ungated attribute, can be used on all release channels
+    Ungated,
+}
+
+// fn() is not Debug
+impl ::std::fmt::Debug for AttributeGate {
+    fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        match *self {
+            Gated(ref name, ref expl, _) => write!(fmt, "Gated({}, {})", name, expl),
+            Ungated => write!(fmt, "Ungated")
+        }
+    }
+}
+
+macro_rules! cfg_fn {
+    ($field: ident) => {{
+        fn f(features: &Features) -> bool {
+            features.$field
+        }
+        f as fn(&Features) -> bool
+    }}
 }
 
 // Attributes that have a special meaning to rustc or rustdoc
@@ -312,88 +391,112 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
     // RFC #1445.
     ("structural_match", Whitelisted, Gated("structural_match",
                                             "the semantics of constant patterns is \
-                                             not yet settled")),
+                                             not yet settled",
+                                            cfg_fn!(structural_match))),
 
     // Not used any more, but we can't feature gate it
     ("no_stack_check", Normal, Ungated),
 
     ("plugin", CrateLevel, Gated("plugin",
                                  "compiler plugins are experimental \
-                                  and possibly buggy")),
+                                  and possibly buggy",
+                                 cfg_fn!(plugin))),
+
     ("no_std", CrateLevel, Ungated),
     ("no_core", CrateLevel, Gated("no_core",
-                                  "no_core is experimental")),
+                                  "no_core is experimental",
+                                  cfg_fn!(no_core))),
     ("lang", Normal, Gated("lang_items",
-                           "language items are subject to change")),
+                           "language items are subject to change",
+                           cfg_fn!(lang_items))),
     ("linkage", Whitelisted, Gated("linkage",
                                    "the `linkage` attribute is experimental \
-                                    and not portable across platforms")),
+                                    and not portable across platforms",
+                                   cfg_fn!(linkage))),
     ("thread_local", Whitelisted, Gated("thread_local",
                                         "`#[thread_local]` is an experimental feature, and does \
                                          not currently handle destructors. There is no \
                                          corresponding `#[task_local]` mapping to the task \
-                                         model")),
+                                         model",
+                                        cfg_fn!(thread_local))),
 
     ("rustc_on_unimplemented", Normal, Gated("on_unimplemented",
                                              "the `#[rustc_on_unimplemented]` attribute \
-                                              is an experimental feature")),
+                                              is an experimental feature",
+                                             cfg_fn!(on_unimplemented))),
     ("allocator", Whitelisted, Gated("allocator",
-                                     "the `#[allocator]` attribute is an experimental feature")),
+                                     "the `#[allocator]` attribute is an experimental feature",
+                                     cfg_fn!(allocator))),
     ("needs_allocator", Normal, Gated("needs_allocator",
                                       "the `#[needs_allocator]` \
                                        attribute is an experimental \
-                                       feature")),
+                                       feature",
+                                      cfg_fn!(needs_allocator))),
     ("rustc_variance", Normal, Gated("rustc_attrs",
                                      "the `#[rustc_variance]` attribute \
                                       is just used for rustc unit tests \
-                                      and will never be stable")),
+                                      and will never be stable",
+                                     cfg_fn!(rustc_attrs))),
     ("rustc_error", Whitelisted, Gated("rustc_attrs",
                                        "the `#[rustc_error]` attribute \
                                         is just used for rustc unit tests \
-                                        and will never be stable")),
+                                        and will never be stable",
+                                       cfg_fn!(rustc_attrs))),
     ("rustc_if_this_changed", Whitelisted, Gated("rustc_attrs",
-                                       "the `#[rustc_if_this_changed]` attribute \
-                                        is just used for rustc unit tests \
-                                        and will never be stable")),
+                                                 "the `#[rustc_if_this_changed]` attribute \
+                                                  is just used for rustc unit tests \
+                                                  and will never be stable",
+                                                 cfg_fn!(rustc_attrs))),
     ("rustc_then_this_would_need", Whitelisted, Gated("rustc_attrs",
-                                       "the `#[rustc_if_this_changed]` attribute \
-                                        is just used for rustc unit tests \
-                                        and will never be stable")),
+                                                      "the `#[rustc_if_this_changed]` attribute \
+                                                       is just used for rustc unit tests \
+                                                       and will never be stable",
+                                                      cfg_fn!(rustc_attrs))),
     ("rustc_dirty", Whitelisted, Gated("rustc_attrs",
                                        "the `#[rustc_dirty]` attribute \
                                         is just used for rustc unit tests \
-                                        and will never be stable")),
+                                        and will never be stable",
+                                       cfg_fn!(rustc_attrs))),
     ("rustc_clean", Whitelisted, Gated("rustc_attrs",
                                        "the `#[rustc_clean]` attribute \
                                         is just used for rustc unit tests \
-                                        and will never be stable")),
+                                        and will never be stable",
+                                       cfg_fn!(rustc_attrs))),
     ("rustc_symbol_name", Whitelisted, Gated("rustc_attrs",
-                                       "internal rustc attributes will never be stable")),
+                                             "internal rustc attributes will never be stable",
+                                             cfg_fn!(rustc_attrs))),
     ("rustc_item_path", Whitelisted, Gated("rustc_attrs",
-                                       "internal rustc attributes will never be stable")),
+                                           "internal rustc attributes will never be stable",
+                                           cfg_fn!(rustc_attrs))),
     ("rustc_move_fragments", Normal, Gated("rustc_attrs",
                                            "the `#[rustc_move_fragments]` attribute \
                                             is just used for rustc unit tests \
-                                            and will never be stable")),
+                                            and will never be stable",
+                                           cfg_fn!(rustc_attrs))),
     ("rustc_mir", Whitelisted, Gated("rustc_attrs",
                                      "the `#[rustc_mir]` attribute \
                                       is just used for rustc unit tests \
-                                      and will never be stable")),
+                                      and will never be stable",
+                                     cfg_fn!(rustc_attrs))),
     ("rustc_no_mir", Whitelisted, Gated("rustc_attrs",
                                         "the `#[rustc_no_mir]` attribute \
                                          is just used to make tests pass \
-                                         and will never be stable")),
+                                         and will never be stable",
+                                        cfg_fn!(rustc_attrs))),
 
     ("allow_internal_unstable", Normal, Gated("allow_internal_unstable",
-                                              EXPLAIN_ALLOW_INTERNAL_UNSTABLE)),
+                                              EXPLAIN_ALLOW_INTERNAL_UNSTABLE,
+                                              cfg_fn!(allow_internal_unstable))),
 
     ("fundamental", Whitelisted, Gated("fundamental",
                                        "the `#[fundamental]` attribute \
-                                        is an experimental feature")),
+                                        is an experimental feature",
+                                       cfg_fn!(fundamental))),
 
     ("linked_from", Normal, Gated("linked_from",
                                   "the `#[linked_from]` attribute \
-                                   is an experimental feature")),
+                                   is an experimental feature",
+                                  cfg_fn!(linked_from))),
 
     // FIXME: #14408 whitelist docs since rustdoc looks at them
     ("doc", Whitelisted, Ungated),
@@ -403,7 +506,8 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
     ("cold", Whitelisted, Ungated),
     ("naked", Whitelisted, Gated("naked_functions",
                                  "the `#[naked]` attribute \
-                                  is an experimental feature")),
+                                  is an experimental feature",
+                                 cfg_fn!(naked_functions))),
     ("export_name", Whitelisted, Ungated),
     ("inline", Whitelisted, Ungated),
     ("link", Whitelisted, Ungated),
@@ -413,24 +517,30 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
     ("no_mangle", Whitelisted, Ungated),
     ("no_debug", Whitelisted, Gated("no_debug",
                                     "the `#[no_debug]` attribute \
-                                     is an experimental feature")),
+                                     is an experimental feature",
+                                    cfg_fn!(no_debug))),
     ("omit_gdb_pretty_printer_section", Whitelisted, Gated("omit_gdb_pretty_printer_section",
                                                        "the `#[omit_gdb_pretty_printer_section]` \
                                                         attribute is just used for the Rust test \
-                                                        suite")),
+                                                        suite",
+                                                       cfg_fn!(omit_gdb_pretty_printer_section))),
     ("unsafe_no_drop_flag", Whitelisted, Gated("unsafe_no_drop_flag",
                                                "unsafe_no_drop_flag has unstable semantics \
-                                                and may be removed in the future")),
+                                                and may be removed in the future",
+                                               cfg_fn!(unsafe_no_drop_flag))),
     ("unsafe_destructor_blind_to_params",
      Normal,
      Gated("dropck_parametricity",
            "unsafe_destructor_blind_to_params has unstable semantics \
-            and may be removed in the future")),
-    ("unwind", Whitelisted, Gated("unwind_attributes", "#[unwind] is experimental")),
+            and may be removed in the future",
+           cfg_fn!(dropck_parametricity))),
+    ("unwind", Whitelisted, Gated("unwind_attributes", "#[unwind] is experimental",
+                                  cfg_fn!(unwind_attributes))),
 
     // used in resolve
     ("prelude_import", Whitelisted, Gated("prelude_import",
-                                          "`#[prelude_import]` is for use by rustc only")),
+                                          "`#[prelude_import]` is for use by rustc only",
+                                          cfg_fn!(prelude_import))),
 
     // FIXME: #14407 these are only looked at on-demand so we can't
     // guarantee they'll have already been checked
@@ -441,9 +551,11 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
     ("deprecated", Normal, Ungated),
 
     ("rustc_paren_sugar", Normal, Gated("unboxed_closures",
-                                        "unboxed_closures are still evolving")),
+                                        "unboxed_closures are still evolving",
+                                        cfg_fn!(unboxed_closures))),
     ("rustc_reflect_like", Whitelisted, Gated("reflect",
-                                              "defining reflective traits is still evolving")),
+                                              "defining reflective traits is still evolving",
+                                              cfg_fn!(reflect))),
 
     // Crate level attributes
     ("crate_name", CrateLevel, Ungated),
@@ -456,21 +568,12 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
     ("recursion_limit", CrateLevel, Ungated),
 ];
 
-macro_rules! cfg_fn {
-    (|$x: ident| $e: expr) => {{
-        fn f($x: &Features) -> bool {
-            $e
-        }
-        f as fn(&Features) -> bool
-    }}
-}
 // cfg(...)'s that are feature gated
 const GATED_CFGS: &'static [(&'static str, &'static str, fn(&Features) -> bool)] = &[
     // (name in cfg, feature, function to check if the feature is enabled)
-    ("target_feature", "cfg_target_feature", cfg_fn!(|x| x.cfg_target_feature)),
-    ("target_vendor", "cfg_target_vendor", cfg_fn!(|x| x.cfg_target_vendor)),
-    ("target_thread_local", "cfg_target_thread_local",
-     cfg_fn!(|x| x.cfg_target_thread_local)),
+    ("target_feature", "cfg_target_feature", cfg_fn!(cfg_target_feature)),
+    ("target_vendor", "cfg_target_vendor", cfg_fn!(cfg_target_vendor)),
+    ("target_thread_local", "cfg_target_thread_local", cfg_fn!(cfg_target_thread_local)),
 ];
 
 #[derive(Debug, Eq, PartialEq)]
@@ -551,172 +654,38 @@ impl GatedCfg {
     }
 }
 
-
-#[derive(PartialEq, Copy, Clone, Debug)]
-pub enum AttributeType {
-    /// Normal, builtin attribute that is consumed
-    /// by the compiler before the unused_attribute check
-    Normal,
-
-    /// Builtin attribute that may not be consumed by the compiler
-    /// before the unused_attribute check. These attributes
-    /// will be ignored by the unused_attribute lint
-    Whitelisted,
-
-    /// Builtin attribute that is only allowed at the crate level
-    CrateLevel,
-}
-
-#[derive(PartialEq, Copy, Clone, Debug)]
-pub enum AttributeGate {
-    /// Is gated by a given feature gate and reason
-    Gated(&'static str, &'static str),
-
-    /// Ungated attribute, can be used on all release channels
-    Ungated,
-}
-
-/// A set of features to be used by later passes.
-pub struct Features {
-    pub unboxed_closures: bool,
-    pub rustc_diagnostic_macros: bool,
-    pub allow_quote: bool,
-    pub allow_asm: bool,
-    pub allow_log_syntax: bool,
-    pub allow_concat_idents: bool,
-    pub allow_trace_macros: bool,
-    pub allow_internal_unstable: bool,
-    pub allow_custom_derive: bool,
-    pub allow_placement_in: bool,
-    pub allow_box: bool,
-    pub allow_pushpop_unsafe: bool,
-    pub allow_inclusive_range: bool,
-    pub simd_ffi: bool,
-    pub unmarked_api: bool,
-    /// spans of #![feature] attrs for stable language features. for error reporting
-    pub declared_stable_lang_features: Vec<Span>,
-    /// #![feature] attrs for non-language (library) features
-    pub declared_lib_features: Vec<(InternedString, Span)>,
-    pub const_fn: bool,
-    pub const_indexing: bool,
-    pub static_recursion: bool,
-    pub default_type_parameter_fallback: bool,
-    pub rustc_attrs: bool,
-    pub type_macros: bool,
-    pub cfg_target_feature: bool,
-    pub cfg_target_vendor: bool,
-    pub cfg_target_thread_local: bool,
-    pub staged_api: bool,
-    pub stmt_expr_attributes: bool,
-    pub deprecated: bool,
-    pub question_mark: bool,
-    pub specialization: bool,
-    pub pub_restricted: bool,
-}
-
-impl Features {
-    pub fn new() -> Features {
-        Features {
-            unboxed_closures: false,
-            rustc_diagnostic_macros: false,
-            allow_quote: false,
-            allow_asm: false,
-            allow_log_syntax: false,
-            allow_concat_idents: false,
-            allow_trace_macros: false,
-            allow_internal_unstable: false,
-            allow_custom_derive: false,
-            allow_placement_in: false,
-            allow_box: false,
-            allow_pushpop_unsafe: false,
-            allow_inclusive_range: false,
-            simd_ffi: false,
-            unmarked_api: false,
-            declared_stable_lang_features: Vec::new(),
-            declared_lib_features: Vec::new(),
-            const_fn: false,
-            const_indexing: false,
-            static_recursion: false,
-            default_type_parameter_fallback: false,
-            rustc_attrs: false,
-            type_macros: false,
-            cfg_target_feature: false,
-            cfg_target_vendor: false,
-            cfg_target_thread_local: false,
-            staged_api: false,
-            stmt_expr_attributes: false,
-            deprecated: false,
-            question_mark: false,
-            specialization: false,
-            pub_restricted: false,
-        }
-    }
-}
-
-const EXPLAIN_BOX_SYNTAX: &'static str =
-    "box expression syntax is experimental; you can call `Box::new` instead.";
-
-const EXPLAIN_PLACEMENT_IN: &'static str =
-    "placement-in expression syntax is experimental and subject to change.";
-
-const EXPLAIN_PUSHPOP_UNSAFE: &'static str =
-    "push/pop_unsafe macros are experimental and subject to change.";
-
-const EXPLAIN_STMT_ATTR_SYNTAX: &'static str =
-    "attributes on non-item statements and expressions are experimental.";
-
-pub fn check_for_box_syntax(f: Option<&Features>, diag: &Handler, span: Span) {
-    if let Some(&Features { allow_box: true, .. }) = f {
-        return;
-    }
-    emit_feature_err(diag, "box_syntax", span, GateIssue::Language, EXPLAIN_BOX_SYNTAX);
-}
-
-pub fn check_for_placement_in(f: Option<&Features>, diag: &Handler, span: Span) {
-    if let Some(&Features { allow_placement_in: true, .. }) = f {
-        return;
-    }
-    emit_feature_err(diag, "placement_in_syntax", span, GateIssue::Language, EXPLAIN_PLACEMENT_IN);
-}
-
-pub fn check_for_pushpop_syntax(f: Option<&Features>, diag: &Handler, span: Span) {
-    if let Some(&Features { allow_pushpop_unsafe: true, .. }) = f {
-        return;
-    }
-    emit_feature_err(diag, "pushpop_unsafe", span, GateIssue::Language, EXPLAIN_PUSHPOP_UNSAFE);
-}
-
 struct Context<'a> {
-    features: Vec<&'static str>,
+    features: &'a Features,
     span_handler: &'a Handler,
     cm: &'a CodeMap,
     plugin_attributes: &'a [(String, AttributeType)],
 }
 
-impl<'a> Context<'a> {
-    fn enable_feature(&mut self, feature: &'static str) {
-        debug!("enabling feature: {}", feature);
-        self.features.push(feature);
-    }
-
-    fn gate_feature(&self, feature: &str, span: Span, explain: &str) {
-        let has_feature = self.has_feature(feature);
-        debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", feature, span, has_feature);
-        if !has_feature && !self.cm.span_allows_unstable(span) {
-            emit_feature_err(self.span_handler, feature, span, GateIssue::Language, explain);
+macro_rules! gate_feature_fn {
+    ($cx: expr, $has_feature: expr, $span: expr, $name: expr, $explain: expr) => {{
+        let (cx, has_feature, span, name, explain) = ($cx, $has_feature, $span, $name, $explain);
+        let has_feature: bool = has_feature(&$cx.features);
+        debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", name, span, has_feature);
+        if !has_feature && !cx.cm.span_allows_unstable(span) {
+            emit_feature_err(cx.span_handler, name, span, GateIssue::Language, explain);
         }
+    }}
+}
+
+macro_rules! gate_feature {
+    ($cx: expr, $feature: ident, $span: expr, $explain: expr) => {
+        gate_feature_fn!($cx, |x:&Features| x.$feature, $span, stringify!($feature), $explain)
     }
-    fn has_feature(&self, feature: &str) -> bool {
-        self.features.iter().any(|&n| n == feature)
-    }
+}
 
+impl<'a> Context<'a> {
     fn check_attribute(&self, attr: &ast::Attribute, is_macro: bool) {
         debug!("check_attribute(attr = {:?})", attr);
         let name = &*attr.name();
-        for &(n, ty, gateage) in KNOWN_ATTRIBUTES {
+        for &(n, ty, ref gateage) in KNOWN_ATTRIBUTES {
             if n == name {
-                if let Gated(gate, desc) = gateage {
-                    self.gate_feature(gate, attr.span, desc);
+                if let &Gated(ref name, ref desc, ref has_feature) = gateage {
+                    gate_feature_fn!(self, has_feature, attr.span, name, desc);
                 }
                 debug!("check_attribute: {:?} is known, {:?}, {:?}", name, ty, gateage);
                 return;
@@ -732,41 +701,50 @@ impl<'a> Context<'a> {
             }
         }
         if name.starts_with("rustc_") {
-            self.gate_feature("rustc_attrs", attr.span,
-                              "unless otherwise specified, attributes \
-                               with the prefix `rustc_` \
-                               are reserved for internal compiler diagnostics");
+            gate_feature!(self, rustc_attrs, attr.span,
+                          "unless otherwise specified, attributes \
+                           with the prefix `rustc_` \
+                           are reserved for internal compiler diagnostics");
         } else if name.starts_with("derive_") {
-            self.gate_feature("custom_derive", attr.span,
-                              "attributes of the form `#[derive_*]` are reserved \
-                               for the compiler");
+            gate_feature!(self, custom_derive, attr.span, EXPLAIN_DERIVE_UNDERSCORE);
         } else {
             // Only run the custom attribute lint during regular
             // feature gate checking. Macro gating runs
             // before the plugin attributes are registered
             // so we skip this then
             if !is_macro {
-                self.gate_feature("custom_attribute", attr.span,
-                           &format!("The attribute `{}` is currently \
-                                    unknown to the compiler and \
-                                    may have meaning \
-                                    added to it in the future",
-                                    name));
+                gate_feature!(self, custom_attribute, attr.span,
+                              &format!("The attribute `{}` is currently \
+                                        unknown to the compiler and \
+                                        may have meaning \
+                                        added to it in the future",
+                                       name));
             }
         }
     }
 }
 
+pub fn check_attribute(attr: &ast::Attribute, handler: &Handler,
+                       cm: &CodeMap, features: &Features) {
+    let cx = Context {
+        features: features, span_handler: handler,
+        cm: cm, plugin_attributes: &[]
+    };
+    cx.check_attribute(attr, true);
+}
+
 fn find_lang_feature_issue(feature: &str) -> Option<u32> {
-    let info = KNOWN_FEATURES.iter()
-                              .find(|t| t.0 == feature)
-                              .unwrap();
-    let issue = info.2;
-    if let Active = info.3 {
+    if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.0 == feature) {
+        let issue = info.2;
         // FIXME (#28244): enforce that active features have issue numbers
         // assert!(issue.is_some())
+        issue
+    } else {
+        // search in Accepted or Removed features
+        ACCEPTED_FEATURES.iter().chain(REMOVED_FEATURES.iter())
+            .find(|t| t.0 == feature)
+            .unwrap().2
     }
-    issue
 }
 
 pub enum GateIssue {
@@ -798,6 +776,12 @@ pub fn emit_feature_err(diag: &Handler, feature: &str, span: Span, issue: GateIs
     err.emit();
 }
 
+const EXPLAIN_BOX_SYNTAX: &'static str =
+    "box expression syntax is experimental; you can call `Box::new` instead.";
+
+const EXPLAIN_STMT_ATTR_SYNTAX: &'static str =
+    "attributes on non-item statements and expressions are experimental.";
+
 pub const EXPLAIN_ASM: &'static str =
     "inline assembly is not stable enough for use and is subject to change";
 
@@ -815,75 +799,23 @@ pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &'static str =
 pub const EXPLAIN_CUSTOM_DERIVE: &'static str =
     "`#[derive]` for custom traits is not stable enough for use and is subject to change";
 
-struct MacroVisitor<'a> {
-    context: &'a Context<'a>
-}
-
-impl<'a, 'v> Visitor<'v> for MacroVisitor<'a> {
-    fn visit_mac(&mut self, mac: &ast::Mac) {
-        let path = &mac.node.path;
-        let name = path.segments.last().unwrap().identifier.name.as_str();
-
-        // Issue 22234: If you add a new case here, make sure to also
-        // add code to catch the macro during or after expansion.
-        //
-        // We still keep this MacroVisitor (rather than *solely*
-        // relying on catching cases during or after expansion) to
-        // catch uses of these macros within conditionally-compiled
-        // code, e.g. `#[cfg]`-guarded functions.
-
-        if name == "asm" {
-            self.context.gate_feature("asm", path.span, EXPLAIN_ASM);
-        }
-
-        else if name == "log_syntax" {
-            self.context.gate_feature("log_syntax", path.span, EXPLAIN_LOG_SYNTAX);
-        }
-
-        else if name == "trace_macros" {
-            self.context.gate_feature("trace_macros", path.span, EXPLAIN_TRACE_MACROS);
-        }
-
-        else if name == "concat_idents" {
-            self.context.gate_feature("concat_idents", path.span, EXPLAIN_CONCAT_IDENTS);
-        }
-    }
-
-    fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
-        self.context.check_attribute(attr, true);
-    }
-
-    fn visit_expr(&mut self, e: &ast::Expr) {
-        // Issue 22181: overloaded-`box` and placement-`in` are
-        // implemented via a desugaring expansion, so their feature
-        // gates go into MacroVisitor since that works pre-expansion.
-        //
-        // Issue 22234: we also check during expansion as well.
-        // But we keep these checks as a pre-expansion check to catch
-        // uses in e.g. conditionalized code.
-
-        if let ast::ExprKind::Box(_) = e.node {
-            self.context.gate_feature("box_syntax", e.span, EXPLAIN_BOX_SYNTAX);
-        }
-
-        if let ast::ExprKind::InPlace(..) = e.node {
-            self.context.gate_feature("placement_in_syntax", e.span, EXPLAIN_PLACEMENT_IN);
-        }
+pub const EXPLAIN_DERIVE_UNDERSCORE: &'static str =
+    "attributes of the form `#[derive_*]` are reserved for the compiler";
 
-        visit::walk_expr(self, e);
-    }
-}
+pub const EXPLAIN_PLACEMENT_IN: &'static str =
+    "placement-in expression syntax is experimental and subject to change.";
 
 struct PostExpansionVisitor<'a> {
     context: &'a Context<'a>,
 }
 
-impl<'a> PostExpansionVisitor<'a> {
-    fn gate_feature(&self, feature: &str, span: Span, explain: &str) {
-        if !self.context.cm.span_allows_unstable(span) {
-            self.context.gate_feature(feature, span, explain)
+macro_rules! gate_feature_post {
+    ($cx: expr, $feature: ident, $span: expr, $explain: expr) => {{
+        let (cx, span) = ($cx, $span);
+        if !cx.context.cm.span_allows_unstable(span) {
+            gate_feature!(cx.context, $feature, span, $explain)
         }
-    }
+    }}
 }
 
 impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
@@ -895,8 +827,8 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
 
     fn visit_name(&mut self, sp: Span, name: ast::Name) {
         if !name.as_str().is_ascii() {
-            self.gate_feature("non_ascii_idents", sp,
-                              "non-ascii idents are not fully supported.");
+            gate_feature_post!(&self, non_ascii_idents, sp,
+                               "non-ascii idents are not fully supported.");
         }
     }
 
@@ -904,60 +836,59 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
         match i.node {
             ast::ItemKind::ExternCrate(_) => {
                 if attr::contains_name(&i.attrs[..], "macro_reexport") {
-                    self.gate_feature("macro_reexport", i.span,
-                                      "macros reexports are experimental \
-                                       and possibly buggy");
+                    gate_feature_post!(&self, macro_reexport, i.span,
+                                       "macros reexports are experimental \
+                                        and possibly buggy");
                 }
             }
 
             ast::ItemKind::ForeignMod(ref foreign_module) => {
                 if attr::contains_name(&i.attrs[..], "link_args") {
-                    self.gate_feature("link_args", i.span,
+                    gate_feature_post!(&self, link_args, i.span,
                                       "the `link_args` attribute is not portable \
                                        across platforms, it is recommended to \
                                        use `#[link(name = \"foo\")]` instead")
                 }
-                let maybe_feature = match foreign_module.abi {
-                    Abi::RustIntrinsic => Some(("intrinsics", "intrinsics are subject to change")),
+                match foreign_module.abi {
+                    Abi::RustIntrinsic =>
+                        gate_feature_post!(&self, intrinsics, i.span,
+                                           "intrinsics are subject to change"),
                     Abi::PlatformIntrinsic => {
-                        Some(("platform_intrinsics",
-                              "platform intrinsics are experimental and possibly buggy"))
+                        gate_feature_post!(&self, platform_intrinsics, i.span,
+                                           "platform intrinsics are experimental \
+                                            and possibly buggy")
                     },
                     Abi::Vectorcall => {
-                        Some(("abi_vectorcall",
-                            "vectorcall is experimental and subject to change"
-                        ))
+                        gate_feature_post!(&self, abi_vectorcall, i.span,
+                                           "vectorcall is experimental and subject to change")
                     }
-                    _ => None
-                };
-                if let Some((feature, msg)) = maybe_feature {
-                    self.gate_feature(feature, i.span, msg)
+                    _ => ()
                 }
             }
 
             ast::ItemKind::Fn(..) => {
                 if attr::contains_name(&i.attrs[..], "plugin_registrar") {
-                    self.gate_feature("plugin_registrar", i.span,
-                                      "compiler plugins are experimental and possibly buggy");
+                    gate_feature_post!(&self, plugin_registrar, i.span,
+                                       "compiler plugins are experimental and possibly buggy");
                 }
                 if attr::contains_name(&i.attrs[..], "start") {
-                    self.gate_feature("start", i.span,
+                    gate_feature_post!(&self, start, i.span,
                                       "a #[start] function is an experimental \
                                        feature whose signature may change \
                                        over time");
                 }
                 if attr::contains_name(&i.attrs[..], "main") {
-                    self.gate_feature("main", i.span,
-                                      "declaration of a nonstandard #[main] \
-                                       function may change over time, for now \
-                                       a top-level `fn main()` is required");
+                    gate_feature_post!(&self, main, i.span,
+                                       "declaration of a nonstandard #[main] \
+                                        function may change over time, for now \
+                                        a top-level `fn main()` is required");
                 }
             }
 
             ast::ItemKind::Struct(..) => {
                 if attr::contains_name(&i.attrs[..], "simd") {
-                    self.gate_feature("simd", i.span,
-                                      "SIMD types are experimental and possibly buggy");
+                    gate_feature_post!(&self, simd, i.span,
+                                       "SIMD types are experimental and possibly buggy");
                     self.context.span_handler.span_warn(i.span,
                                                         "the `#[simd]` attribute is deprecated, \
                                                          use `#[repr(simd)]` instead");
@@ -966,8 +897,9 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
                     if attr.name() == "repr" {
                         for item in attr.meta_item_list().unwrap_or(&[]) {
                             if item.name() == "simd" {
-                                self.gate_feature("repr_simd", i.span,
-                                                  "SIMD types are experimental and possibly buggy");
+                                gate_feature_post!(&self, repr_simd, i.span,
+                                                   "SIMD types are experimental \
+                                                    and possibly buggy");
 
                             }
                         }
@@ -976,19 +908,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
             }
 
             ast::ItemKind::DefaultImpl(..) => {
-                self.gate_feature("optin_builtin_traits",
-                                  i.span,
-                                  "default trait implementations are experimental \
-                                   and possibly buggy");
+                gate_feature_post!(&self, optin_builtin_traits,
+                                   i.span,
+                                   "default trait implementations are experimental \
+                                    and possibly buggy");
             }
 
             ast::ItemKind::Impl(_, polarity, _, _, _, _) => {
                 match polarity {
                     ast::ImplPolarity::Negative => {
-                        self.gate_feature("optin_builtin_traits",
-                                          i.span,
-                                          "negative trait bounds are not yet fully implemented; \
-                                          use marker types for now");
+                        gate_feature_post!(&self, optin_builtin_traits,
+                                           i.span,
+                                           "negative trait bounds are not yet fully implemented; \
+                                            use marker types for now");
                     },
                     _ => {}
                 }
@@ -1001,7 +933,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
     }
 
     fn visit_variant_data(&mut self, s: &'v ast::VariantData, _: ast::Ident,
-                        _: &'v ast::Generics, _: ast::NodeId, span: Span) {
+                          _: &'v ast::Generics, _: ast::NodeId, span: Span) {
         if s.fields().is_empty() {
             if s.is_tuple() {
                 self.context.span_handler.struct_span_err(span, "empty tuple structs and enum \
@@ -1023,7 +955,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
             _ => false
         };
         if links_to_llvm {
-            self.gate_feature("link_llvm_intrinsics", i.span,
+            gate_feature_post!(&self, link_llvm_intrinsics, i.span,
                               "linking to LLVM intrinsics is experimental");
         }
 
@@ -1033,22 +965,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
     fn visit_expr(&mut self, e: &ast::Expr) {
         match e.node {
             ast::ExprKind::Box(_) => {
-                self.gate_feature("box_syntax",
-                                  e.span,
-                                  "box expression syntax is experimental; \
-                                   you can call `Box::new` instead.");
+                gate_feature_post!(&self, box_syntax, e.span, EXPLAIN_BOX_SYNTAX);
             }
             ast::ExprKind::Type(..) => {
-                self.gate_feature("type_ascription", e.span,
+                gate_feature_post!(&self, type_ascription, e.span,
                                   "type ascription is experimental");
             }
             ast::ExprKind::Range(_, _, ast::RangeLimits::Closed) => {
-                self.gate_feature("inclusive_range_syntax",
+                gate_feature_post!(&self, inclusive_range_syntax,
                                   e.span,
                                   "inclusive range syntax is experimental");
             }
             ast::ExprKind::Try(..) => {
-                self.gate_feature("question_mark", e.span, "the `?` operator is not stable");
+                gate_feature_post!(&self, question_mark, e.span, "the `?` operator is not stable");
             }
             _ => {}
         }
@@ -1058,19 +987,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
     fn visit_pat(&mut self, pattern: &ast::Pat) {
         match pattern.node {
             PatKind::Vec(_, Some(_), ref last) if !last.is_empty() => {
-                self.gate_feature("advanced_slice_patterns",
+                gate_feature_post!(&self, advanced_slice_patterns,
                                   pattern.span,
                                   "multiple-element slice matches anywhere \
                                    but at the end of a slice (e.g. \
                                    `[0, ..xs, 0]`) are experimental")
             }
             PatKind::Vec(..) => {
-                self.gate_feature("slice_patterns",
+                gate_feature_post!(&self, slice_patterns,
                                   pattern.span,
                                   "slice pattern syntax is experimental");
             }
             PatKind::Box(..) => {
-                self.gate_feature("box_patterns",
+                gate_feature_post!(&self, box_patterns,
                                   pattern.span,
                                   "box pattern syntax is experimental");
             }
@@ -1088,7 +1017,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
         // check for const fn declarations
         match fn_kind {
             FnKind::ItemFn(_, _, _, ast::Constness::Const, _, _) => {
-                self.gate_feature("const_fn", span, "const fn is unstable");
+                gate_feature_post!(&self, const_fn, span, "const fn is unstable");
             }
             _ => {
                 // stability of const fn methods are covered in
@@ -1100,18 +1029,18 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
 
         match fn_kind {
             FnKind::ItemFn(_, _, _, _, abi, _) if abi == Abi::RustIntrinsic => {
-                self.gate_feature("intrinsics",
+                gate_feature_post!(&self, intrinsics,
                                   span,
                                   "intrinsics are subject to change")
             }
             FnKind::ItemFn(_, _, _, _, abi, _) |
             FnKind::Method(_, &ast::MethodSig { abi, .. }, _) => match abi {
                 Abi::RustCall => {
-                    self.gate_feature("unboxed_closures", span,
+                    gate_feature_post!(&self, unboxed_closures, span,
                         "rust-call ABI is subject to change");
                 },
                 Abi::Vectorcall => {
-                    self.gate_feature("abi_vectorcall", span,
+                    gate_feature_post!(&self, abi_vectorcall, span,
                         "vectorcall is experimental and subject to change");
                 },
                 _ => {}
@@ -1124,17 +1053,17 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
     fn visit_trait_item(&mut self, ti: &'v ast::TraitItem) {
         match ti.node {
             ast::TraitItemKind::Const(..) => {
-                self.gate_feature("associated_consts",
+                gate_feature_post!(&self, associated_consts,
                                   ti.span,
                                   "associated constants are experimental")
             }
             ast::TraitItemKind::Method(ref sig, _) => {
                 if sig.constness == ast::Constness::Const {
-                    self.gate_feature("const_fn", ti.span, "const fn is unstable");
+                    gate_feature_post!(&self, const_fn, ti.span, "const fn is unstable");
                 }
             }
             ast::TraitItemKind::Type(_, Some(_)) => {
-                self.gate_feature("associated_type_defaults", ti.span,
+                gate_feature_post!(&self, associated_type_defaults, ti.span,
                                   "associated type defaults are unstable");
             }
             _ => {}
@@ -1144,20 +1073,20 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
 
     fn visit_impl_item(&mut self, ii: &'v ast::ImplItem) {
         if ii.defaultness == ast::Defaultness::Default {
-            self.gate_feature("specialization",
+            gate_feature_post!(&self, specialization,
                               ii.span,
                               "specialization is unstable");
         }
 
         match ii.node {
             ast::ImplItemKind::Const(..) => {
-                self.gate_feature("associated_consts",
+                gate_feature_post!(&self, associated_consts,
                                   ii.span,
                                   "associated constants are experimental")
             }
             ast::ImplItemKind::Method(ref sig, _) => {
                 if sig.constness == ast::Constness::Const {
-                    self.gate_feature("const_fn", ii.span, "const fn is unstable");
+                    gate_feature_post!(&self, const_fn, ii.span, "const fn is unstable");
                 }
             }
             _ => {}
@@ -1171,26 +1100,12 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
             ast::Visibility::Restricted { ref path, .. } => path.span,
             _ => return,
         };
-        self.gate_feature("pub_restricted", span, "`pub(restricted)` syntax is experimental");
+        gate_feature_post!(&self, pub_restricted, span, "`pub(restricted)` syntax is experimental");
     }
 }
 
-fn check_crate_inner<F>(cm: &CodeMap, span_handler: &Handler,
-                        krate: &ast::Crate,
-                        plugin_attributes: &[(String, AttributeType)],
-                        check: F)
-                       -> Features
-    where F: FnOnce(&mut Context, &ast::Crate)
-{
-    let mut cx = Context {
-        features: Vec::new(),
-        span_handler: span_handler,
-        cm: cm,
-        plugin_attributes: plugin_attributes,
-    };
-
-    let mut accepted_features = Vec::new();
-    let mut unknown_features = Vec::new();
+pub fn get_features(span_handler: &Handler, krate: &ast::Crate) -> Features {
+    let mut features = Features::new();
 
     for attr in &krate.attrs {
         if !attr.check_name("feature") {
@@ -1213,82 +1128,43 @@ fn check_crate_inner<F>(cm: &CodeMap, span_handler: &Handler,
                             continue
                         }
                     };
-                    match KNOWN_FEATURES.iter()
-                                        .find(|& &(n, _, _, _)| name == n) {
-                        Some(&(name, _, _, Active)) => {
-                            cx.enable_feature(name);
-                        }
-                        Some(&(_, _, _, Removed)) => {
-                            span_handler.span_err(mi.span, "feature has been removed");
-                        }
-                        Some(&(_, _, _, Accepted)) => {
-                            accepted_features.push(mi.span);
-                        }
-                        None => {
-                            unknown_features.push((name, mi.span));
-                        }
+                    if let Some(&(_, _, _, setter)) = ACTIVE_FEATURES.iter()
+                        .find(|& &(n, _, _, _)| name == n) {
+                        *(setter(&mut features)) = true;
+                    }
+                    else if let Some(&(_, _, _)) = REMOVED_FEATURES.iter()
+                        .find(|& &(n, _, _)| name == n) {
+                        span_handler.span_err(mi.span, "feature has been removed");
+                    }
+                    else if let Some(&(_, _, _)) = ACCEPTED_FEATURES.iter()
+                        .find(|& &(n, _, _)| name == n) {
+                        features.declared_stable_lang_features.push(mi.span);
+                    } else {
+                        features.declared_lib_features.push((name, mi.span));
                     }
                 }
             }
         }
     }
 
-    check(&mut cx, krate);
-
-    // FIXME (pnkfelix): Before adding the 99th entry below, change it
-    // to a single-pass (instead of N calls to `.has_feature`).
-
-    Features {
-        unboxed_closures: cx.has_feature("unboxed_closures"),
-        rustc_diagnostic_macros: cx.has_feature("rustc_diagnostic_macros"),
-        allow_quote: cx.has_feature("quote"),
-        allow_asm: cx.has_feature("asm"),
-        allow_log_syntax: cx.has_feature("log_syntax"),
-        allow_concat_idents: cx.has_feature("concat_idents"),
-        allow_trace_macros: cx.has_feature("trace_macros"),
-        allow_internal_unstable: cx.has_feature("allow_internal_unstable"),
-        allow_custom_derive: cx.has_feature("custom_derive"),
-        allow_placement_in: cx.has_feature("placement_in_syntax"),
-        allow_box: cx.has_feature("box_syntax"),
-        allow_pushpop_unsafe: cx.has_feature("pushpop_unsafe"),
-        allow_inclusive_range: cx.has_feature("inclusive_range_syntax"),
-        simd_ffi: cx.has_feature("simd_ffi"),
-        unmarked_api: cx.has_feature("unmarked_api"),
-        declared_stable_lang_features: accepted_features,
-        declared_lib_features: unknown_features,
-        const_fn: cx.has_feature("const_fn"),
-        const_indexing: cx.has_feature("const_indexing"),
-        static_recursion: cx.has_feature("static_recursion"),
-        default_type_parameter_fallback: cx.has_feature("default_type_parameter_fallback"),
-        rustc_attrs: cx.has_feature("rustc_attrs"),
-        type_macros: cx.has_feature("type_macros"),
-        cfg_target_feature: cx.has_feature("cfg_target_feature"),
-        cfg_target_vendor: cx.has_feature("cfg_target_vendor"),
-        cfg_target_thread_local: cx.has_feature("cfg_target_thread_local"),
-        staged_api: cx.has_feature("staged_api"),
-        stmt_expr_attributes: cx.has_feature("stmt_expr_attributes"),
-        deprecated: cx.has_feature("deprecated"),
-        question_mark: cx.has_feature("question_mark"),
-        specialization: cx.has_feature("specialization"),
-        pub_restricted: cx.has_feature("pub_restricted"),
-    }
-}
-
-pub fn check_crate_macros(cm: &CodeMap, span_handler: &Handler, krate: &ast::Crate)
--> Features {
-    check_crate_inner(cm, span_handler, krate, &[] as &'static [_],
-                      |ctx, krate| visit::walk_crate(&mut MacroVisitor { context: ctx }, krate))
+    features
 }
 
 pub fn check_crate(cm: &CodeMap, span_handler: &Handler, krate: &ast::Crate,
                    plugin_attributes: &[(String, AttributeType)],
-                   unstable: UnstableFeatures) -> Features
-{
+                   unstable: UnstableFeatures) -> Features {
     maybe_stage_features(span_handler, krate, unstable);
-
-    check_crate_inner(cm, span_handler, krate, plugin_attributes,
-                      |ctx, krate| visit::walk_crate(&mut PostExpansionVisitor { context: ctx },
-                                                     krate))
+    let features = get_features(span_handler, krate);
+    {
+        let ctx = Context {
+            features: &features,
+            span_handler: span_handler,
+            cm: cm,
+            plugin_attributes: plugin_attributes,
+        };
+        visit::walk_crate(&mut PostExpansionVisitor { context: &ctx }, krate);
+    }
+    features
 }
 
 #[derive(Clone, Copy)]
diff --git a/src/libsyntax_ext/deriving/mod.rs b/src/libsyntax_ext/deriving/mod.rs
index 92a141fb4ec86..4ca3196b9c5ec 100644
--- a/src/libsyntax_ext/deriving/mod.rs
+++ b/src/libsyntax_ext/deriving/mod.rs
@@ -96,6 +96,36 @@ fn expand_derive(cx: &mut ExtCtxt,
             let mut found_partial_eq = false;
             let mut found_eq = false;
 
+            // This span is **very** sensitive and crucial to
+            // getting the stability behavior we want. What we are
+            // doing is marking the generated `#[derive_*]` with the
+            // span of the `#[deriving(...)]` attribute (the
+            // entire attribute, not just the `PartialEq` or `Eq`
+            // part), but with the current backtrace. The current
+            // backtrace will contain a topmost entry that IS this
+            // `#[deriving(...)]` attribute and with the
+            // "allow-unstable" flag set to true.
+            //
+            // Note that we do NOT use the span of the `Eq`
+            // text itself. You might think this is
+            // equivalent, because the `Eq` appears within the
+            // `#[deriving(Eq)]` attribute, and hence we would
+            // inherit the "allows unstable" from the
+            // backtrace.  But in fact this is not always the
+            // case. The actual source text that led to
+            // deriving can be `#[$attr]`, for example, where
+            // `$attr == deriving(Eq)`. In that case, the
+            // "#[derive_*]" would be considered to
+            // originate not from the deriving call but from
+            // text outside the deriving call, and hence would
+            // be forbidden from using unstable
+            // content.
+            //
+            // See tests src/run-pass/rfc1445 for
+            // examples. --nmatsakis
+            let span = Span { expn_id: cx.backtrace(), .. span };
+            assert!(cx.parse_sess.codemap().span_allows_unstable(span));
+
             for titem in traits.iter().rev() {
                 let tname = match titem.node {
                     MetaItemKind::Word(ref tname) => tname,
@@ -121,42 +151,13 @@ fn expand_derive(cx: &mut ExtCtxt,
                 }
 
                 // #[derive(Foo, Bar)] expands to #[derive_Foo] #[derive_Bar]
-                item.attrs.push(cx.attribute(titem.span, cx.meta_word(titem.span,
+                item.attrs.push(cx.attribute(span, cx.meta_word(titem.span,
                     intern_and_get_ident(&format!("derive_{}", tname)))));
             }
 
             // RFC #1445. `#[derive(PartialEq, Eq)]` adds a (trusted)
             // `#[structural_match]` attribute.
             if found_partial_eq && found_eq {
-                // This span is **very** sensitive and crucial to
-                // getting the stability behavior we want. What we are
-                // doing is marking `#[structural_match]` with the
-                // span of the `#[deriving(...)]` attribute (the
-                // entire attribute, not just the `PartialEq` or `Eq`
-                // part), but with the current backtrace. The current
-                // backtrace will contain a topmost entry that IS this
-                // `#[deriving(...)]` attribute and with the
-                // "allow-unstable" flag set to true.
-                //
-                // Note that we do NOT use the span of the `Eq`
-                // text itself. You might think this is
-                // equivalent, because the `Eq` appears within the
-                // `#[deriving(Eq)]` attribute, and hence we would
-                // inherit the "allows unstable" from the
-                // backtrace.  But in fact this is not always the
-                // case. The actual source text that led to
-                // deriving can be `#[$attr]`, for example, where
-                // `$attr == deriving(Eq)`. In that case, the
-                // "#[structural_match]" would be considered to
-                // originate not from the deriving call but from
-                // text outside the deriving call, and hence would
-                // be forbidden from using unstable
-                // content.
-                //
-                // See tests src/run-pass/rfc1445 for
-                // examples. --nmatsakis
-                let span = Span { expn_id: cx.backtrace(), .. span };
-                assert!(cx.parse_sess.codemap().span_allows_unstable(span));
                 debug!("inserting structural_match with span {:?}", span);
                 let structural_match = intern_and_get_ident("structural_match");
                 item.attrs.push(cx.attribute(span,
@@ -188,6 +189,39 @@ macro_rules! derive_traits {
                               mitem: &MetaItem,
                               annotatable: &Annotatable,
                               push: &mut FnMut(Annotatable)) {
+                        if !ecx.parse_sess.codemap().span_allows_unstable(sp)
+                            && !ecx.ecfg.features.unwrap().custom_derive {
+                            // FIXME:
+                            // https://github.com/rust-lang/rust/pull/32671#issuecomment-206245303
+                            // This is just to avoid breakage with syntex.
+                            // Remove that to spawn an error instead.
+                            let cm = ecx.parse_sess.codemap();
+                            let parent = cm.with_expn_info(ecx.backtrace(),
+                                                           |info| info.unwrap().call_site.expn_id);
+                            cm.with_expn_info(parent, |info| {
+                                if info.is_some() {
+                                    let mut w = ecx.parse_sess.span_diagnostic.struct_span_warn(
+                                        sp, feature_gate::EXPLAIN_DERIVE_UNDERSCORE,
+                                    );
+                                    if option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_none() {
+                                        w.fileline_help(
+                                            sp, &format!("add #![feature(custom_derive)] to \
+                                                          the crate attributes to enable")
+                                        );
+                                    }
+                                    w.emit();
+                                } else {
+                                    feature_gate::emit_feature_err(
+                                        &ecx.parse_sess.span_diagnostic,
+                                        "custom_derive", sp, feature_gate::GateIssue::Language,
+                                        feature_gate::EXPLAIN_DERIVE_UNDERSCORE
+                                    );
+
+                                    return;
+                                }
+                            })
+                        }
+
                         warn_if_deprecated(ecx, sp, $name);
                         $func(ecx, sp, mitem, annotatable, push);
                     }
diff --git a/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs b/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs
index c9251c925cc40..9ebf8a9b74a6c 100644
--- a/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs
+++ b/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs
@@ -11,8 +11,8 @@
 macro_rules! bar {
     () => {
         // more layers don't help:
-        #[allow_internal_unstable]
-        macro_rules! baz { //~ ERROR allow_internal_unstable side-steps
+        #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+        macro_rules! baz {
             () => {}
         }
     }
diff --git a/src/test/compile-fail/trace_macros-gate2.rs b/src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs
similarity index 54%
rename from src/test/compile-fail/trace_macros-gate2.rs
rename to src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs
index 71cc45e132d33..b186278ef8b7b 100644
--- a/src/test/compile-fail/trace_macros-gate2.rs
+++ b/src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs
@@ -1,4 +1,4 @@
-// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
 // file at the top-level directory of this distribution and at
 // http://rust-lang.org/COPYRIGHT.
 //
@@ -8,13 +8,10 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// Test that the trace_macros feature gate is on.
+// checks that this attribute is caught on non-macro items.
+// this needs a different test since this is done after expansion
 
-fn main() {
-    // (Infrastructure does not attempt to detect uses in macro definitions.)
-    macro_rules! expando {
-        ($x: ident) => { trace_macros!($x) }
-    }
+#[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+struct S;
 
-    expando!(true); //~ ERROR `trace_macros` is not stable
-}
+fn main() {}
diff --git a/src/test/compile-fail/issue-32655.rs b/src/test/compile-fail/issue-32655.rs
new file mode 100644
index 0000000000000..edd7fe4a1e588
--- /dev/null
+++ b/src/test/compile-fail/issue-32655.rs
@@ -0,0 +1,33 @@
+// Copyright 2016 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.
+
+#![allow(dead_code)]
+#![feature(rustc_attrs)]
+
+macro_rules! foo (
+    () => (
+        #[derive_Clone] //~ WARN attributes of the form
+        struct T;
+    );
+);
+
+macro_rules! bar (
+    ($e:item) => ($e)
+);
+
+foo!();
+
+bar!(
+    #[derive_Clone] //~ WARN attributes of the form
+    struct S;
+);
+
+#[rustc_error]
+fn main() {} //~ ERROR compilation successful
diff --git a/src/test/compile-fail/issue-32782.rs b/src/test/compile-fail/issue-32782.rs
new file mode 100644
index 0000000000000..696ea0ef5473b
--- /dev/null
+++ b/src/test/compile-fail/issue-32782.rs
@@ -0,0 +1,23 @@
+// Copyright 2016 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.
+
+macro_rules! bar (
+    () => ()
+);
+
+macro_rules! foo (
+    () => (
+        #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+        bar!();
+    );
+);
+
+foo!();
+fn main() {}
diff --git a/src/test/compile-fail/trace_macros-gate.rs b/src/test/compile-fail/trace_macros-gate.rs
index 6473bcece91b6..d627de24d6794 100644
--- a/src/test/compile-fail/trace_macros-gate.rs
+++ b/src/test/compile-fail/trace_macros-gate.rs
@@ -26,5 +26,5 @@ fn main() {
         ($x: ident) => { trace_macros!($x) }
     }
 
-    expando!(true);
+    expando!(true); //~ ERROR `trace_macros` is not stable
 }
diff --git a/src/test/compile-fail/trace_macros-gate3.rs b/src/test/compile-fail/trace_macros-gate3.rs
deleted file mode 100644
index 66d03cf9d8046..0000000000000
--- a/src/test/compile-fail/trace_macros-gate3.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-// Test that the trace_macros feature gate is on.
-
-pub fn main() {
-    println!("arg: {}", trace_macros!()); //~ ERROR `trace_macros` is not stable
-    println!("arg: {}", trace_macros!(1)); //~ ERROR `trace_macros` is not stable
-    println!("arg: {}", trace_macros!(ident)); //~ ERROR `trace_macros` is not stable
-    println!("arg: {}", trace_macros!(for)); //~ ERROR `trace_macros` is not stable
-    println!("arg: {}", trace_macros!(true,)); //~ ERROR `trace_macros` is not stable
-    println!("arg: {}", trace_macros!(false 1)); //~ ERROR `trace_macros` is not stable
-}
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index 991cf201d44f0..0b989d92b3d1d 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -136,18 +136,18 @@ fn collect_lang_features(path: &Path) -> Vec<Feature> {
 
     let mut features = Vec::new();
     for line in contents.lines().map(|l| l.trim()) {
-        if !STATUSES.iter().any(|s| line.contains(s) && line.starts_with("(")) {
+        if !STATUSES.iter().any(|s| line.starts_with(&format!("({}", s))) {
             continue
         }
         let mut parts = line.split(",");
-        let name = parts.next().unwrap().replace("\"", "").replace("(", "");
-        let since = parts.next().unwrap().trim().replace("\"", "");
-        let status = match parts.skip(1).next().unwrap() {
-            s if s.contains("Active") => "unstable",
-            s if s.contains("Removed") => "unstable",
-            s if s.contains("Accepted") => "stable",
+        let status = match &parts.next().unwrap().trim().replace("(", "")[..] {
+            "active"   => "unstable",
+            "removed"  => "unstable",
+            "accepted" => "stable",
             s => panic!("unknown status: {}", s),
         };
+        let name = parts.next().unwrap().trim().to_owned();
+        let since = parts.next().unwrap().trim().replace("\"", "");
 
         features.push(Feature {
             name: name,