about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock18
-rw-r--r--src/librustc/Cargo.toml1
-rw-r--r--src/librustc/arena.rs2
-rw-r--r--src/librustc/ich/impls_syntax.rs3
-rw-r--r--src/librustc/lint/levels.rs6
-rw-r--r--src/librustc/middle/stability.rs24
-rw-r--r--src/librustc/query/mod.rs2
-rw-r--r--src/librustc/session/config.rs6
-rw-r--r--src/librustc/session/mod.rs7
-rw-r--r--src/librustc/ty/context.rs3
-rw-r--r--src/librustc/ty/query/mod.rs1
-rw-r--r--src/librustc_codegen_llvm/lib.rs1
-rw-r--r--src/librustc_codegen_llvm/llvm_util.rs2
-rw-r--r--src/librustc_driver/Cargo.toml1
-rw-r--r--src/librustc_driver/lib.rs24
-rw-r--r--src/librustc_feature/Cargo.toml15
-rw-r--r--src/librustc_feature/accepted.rs (renamed from src/libsyntax/feature_gate/accepted.rs)2
-rw-r--r--src/librustc_feature/active.rs (renamed from src/libsyntax/feature_gate/active.rs)19
-rw-r--r--src/librustc_feature/builtin_attrs.rs (renamed from src/libsyntax/feature_gate/builtin_attrs.rs)71
-rw-r--r--src/librustc_feature/lib.rs137
-rw-r--r--src/librustc_feature/removed.rs (renamed from src/libsyntax/feature_gate/removed.rs)2
-rw-r--r--src/librustc_lint/Cargo.toml1
-rw-r--r--src/librustc_lint/builtin.rs8
-rw-r--r--src/librustc_lint/unused.rs5
-rw-r--r--src/librustc_metadata/native_libs.rs36
-rw-r--r--src/librustc_mir/transform/check_consts/ops.rs48
-rw-r--r--src/librustc_parse/Cargo.toml1
-rw-r--r--src/librustc_parse/config.rs14
-rw-r--r--src/librustc_parse/validate_attr.rs18
-rw-r--r--src/librustc_passes/Cargo.toml1
-rw-r--r--src/librustc_passes/ast_validation.rs3
-rw-r--r--src/librustc_passes/check_const.rs11
-rw-r--r--src/librustc_resolve/Cargo.toml1
-rw-r--r--src/librustc_resolve/build_reduced_graph.rs4
-rw-r--r--src/librustc_resolve/diagnostics.rs2
-rw-r--r--src/librustc_resolve/macros.rs15
-rw-r--r--src/librustc_typeck/astconv.rs5
-rw-r--r--src/librustc_typeck/check/coercion.rs12
-rw-r--r--src/librustc_typeck/check/mod.rs32
-rw-r--r--src/librustc_typeck/check/wfcheck.rs6
-rw-r--r--src/librustc_typeck/collect.rs12
-rw-r--r--src/librustdoc/clean/cfg.rs2
-rw-r--r--src/librustdoc/core.rs2
-rw-r--r--src/librustdoc/externalfiles.rs2
-rw-r--r--src/librustdoc/html/render.rs2
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/librustdoc/markdown.rs2
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs2
-rw-r--r--src/librustdoc/test.rs2
-rw-r--r--src/libsyntax/Cargo.toml1
-rw-r--r--src/libsyntax/attr/builtin.rs57
-rw-r--r--src/libsyntax/feature_gate/check.rs126
-rw-r--r--src/libsyntax/feature_gate/mod.rs71
-rw-r--r--src/libsyntax/lib.rs5
-rw-r--r--src/libsyntax/sess.rs4
-rw-r--r--src/libsyntax_expand/Cargo.toml1
-rw-r--r--src/libsyntax_expand/expand.rs33
-rw-r--r--src/libsyntax_expand/mbe/macro_rules.rs2
-rw-r--r--src/libsyntax_ext/Cargo.toml1
-rw-r--r--src/libsyntax_ext/test_harness.rs2
-rw-r--r--src/libsyntax_ext/util.rs2
-rw-r--r--src/tools/tidy/src/features.rs2
62 files changed, 444 insertions, 460 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d27eec893b0..67259aed8eb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3199,6 +3199,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_fs_util",
  "rustc_index",
  "rustc_macros",
@@ -3574,6 +3575,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_interface",
  "rustc_lint",
  "rustc_metadata",
@@ -3608,6 +3610,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustc_feature"
+version = "0.0.0"
+dependencies = [
+ "lazy_static 1.3.0",
+ "rustc_data_structures",
+ "syntax_pos",
+]
+
+[[package]]
 name = "rustc_fs_util"
 version = "0.0.0"
 
@@ -3682,6 +3693,7 @@ dependencies = [
  "rustc",
  "rustc_data_structures",
  "rustc_error_codes",
+ "rustc_feature",
  "rustc_index",
  "rustc_target",
  "syntax",
@@ -3786,6 +3798,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_lexer",
  "rustc_target",
  "smallvec 1.0.0",
@@ -3802,6 +3815,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_index",
  "rustc_parse",
  "rustc_target",
@@ -3844,6 +3858,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_metadata",
  "smallvec 1.0.0",
  "syntax",
@@ -4442,6 +4457,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_index",
  "rustc_lexer",
  "rustc_macros",
@@ -4458,6 +4474,7 @@ dependencies = [
  "log",
  "rustc_data_structures",
  "rustc_errors",
+ "rustc_feature",
  "rustc_lexer",
  "rustc_parse",
  "serialize",
@@ -4475,6 +4492,7 @@ dependencies = [
  "rustc_data_structures",
  "rustc_error_codes",
  "rustc_errors",
+ "rustc_feature",
  "rustc_parse",
  "rustc_target",
  "smallvec 1.0.0",
diff --git a/src/librustc/Cargo.toml b/src/librustc/Cargo.toml
index 6c1695a4eac..fb30d6c519c 100644
--- a/src/librustc/Cargo.toml
+++ b/src/librustc/Cargo.toml
@@ -22,6 +22,7 @@ rustc-rayon = "0.3.0"
 rustc-rayon-core = "0.3.0"
 polonius-engine  = "0.10.0"
 rustc_apfloat = { path = "../librustc_apfloat" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_target = { path = "../librustc_target" }
 rustc_macros = { path = "../librustc_macros" }
 rustc_data_structures = { path = "../librustc_data_structures" }
diff --git a/src/librustc/arena.rs b/src/librustc/arena.rs
index 193b04eabb3..1d6a3420ed9 100644
--- a/src/librustc/arena.rs
+++ b/src/librustc/arena.rs
@@ -101,7 +101,7 @@ macro_rules! arena_types {
             [few] resolve_lifetimes: rustc::middle::resolve_lifetime::ResolveLifetimes,
             [few] lint_levels: rustc::lint::LintLevelMap,
             [few] stability_index: rustc::middle::stability::Index<'tcx>,
-            [few] features: syntax::feature_gate::Features,
+            [few] features: rustc_feature::Features,
             [few] all_traits: Vec<rustc::hir::def_id::DefId>,
             [few] privacy_access_levels: rustc::middle::privacy::AccessLevels,
             [few] target_features_whitelist: rustc_data_structures::fx::FxHashMap<
diff --git a/src/librustc/ich/impls_syntax.rs b/src/librustc/ich/impls_syntax.rs
index 144980c53eb..6499e56325a 100644
--- a/src/librustc/ich/impls_syntax.rs
+++ b/src/librustc/ich/impls_syntax.rs
@@ -4,7 +4,6 @@
 use crate::ich::StableHashingContext;
 
 use syntax::ast;
-use syntax::feature_gate;
 use syntax_pos::SourceFile;
 
 use crate::hir::def_id::{DefId, CrateNum, CRATE_DEF_INDEX};
@@ -156,7 +155,7 @@ fn stable_normalized_pos(np: ::syntax_pos::NormalizedPos,
 }
 
 
-impl<'tcx> HashStable<StableHashingContext<'tcx>> for feature_gate::Features {
+impl<'tcx> HashStable<StableHashingContext<'tcx>> for rustc_feature::Features {
     fn hash_stable(&self, hcx: &mut StableHashingContext<'tcx>, hasher: &mut StableHasher) {
         // Unfortunately we cannot exhaustively list fields here, since the
         // struct is macro generated.
diff --git a/src/librustc/lint/levels.rs b/src/librustc/lint/levels.rs
index 27bf9649324..619ca724214 100644
--- a/src/librustc/lint/levels.rs
+++ b/src/librustc/lint/levels.rs
@@ -232,13 +232,13 @@ impl<'a> LintLevelsBuilder<'a> {
                             // don't have any lint names (`#[level(reason = "foo")]`)
                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
                                 if !self.sess.features_untracked().lint_reasons {
-                                    feature_gate::emit_feature_err(
+                                    feature_gate::feature_err(
                                         &self.sess.parse_sess,
                                         sym::lint_reasons,
                                         item.span,
-                                        feature_gate::GateIssue::Language,
                                         "lint reasons are experimental"
-                                    );
+                                    )
+                                    .emit();
                                 }
                                 reason = Some(rationale);
                             } else {
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 411a47423c5..54aafe2114d 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -13,11 +13,12 @@ use crate::ty::query::Providers;
 use crate::middle::privacy::AccessLevels;
 use crate::session::{DiagnosticMessageId, Session};
 use errors::DiagnosticBuilder;
+use rustc_feature::GateIssue;
 use syntax::symbol::{Symbol, sym};
 use syntax_pos::{Span, MultiSpan};
 use syntax::ast::{Attribute, CRATE_NODE_ID};
 use syntax::errors::Applicability;
-use syntax::feature_gate::{GateIssue, emit_feature_err};
+use syntax::feature_gate::{feature_err, feature_err_issue};
 use syntax::attr::{self, Stability, Deprecation, RustcDeprecation};
 use crate::ty::{self, TyCtxt};
 use crate::util::nodemap::{FxHashSet, FxHashMap};
@@ -512,9 +513,8 @@ pub fn report_unstable(
         if is_soft {
             soft_handler(lint::builtin::SOFT_UNSTABLE, span, &msg)
         } else {
-            emit_feature_err(
-                &sess.parse_sess, feature, span, GateIssue::Library(issue), &msg
-            );
+            feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg)
+                .emit();
         }
     }
 }
@@ -842,15 +842,19 @@ impl Visitor<'tcx> for Checker<'tcx> {
                 let ty = self.tcx.type_of(def_id);
 
                 if adt_def.has_dtor(self.tcx) {
-                    emit_feature_err(&self.tcx.sess.parse_sess,
-                                     sym::untagged_unions, item.span, GateIssue::Language,
-                                     "unions with `Drop` implementations are unstable");
+                    feature_err(
+                        &self.tcx.sess.parse_sess, sym::untagged_unions, item.span,
+                        "unions with `Drop` implementations are unstable"
+                    )
+                    .emit();
                 } else {
                     let param_env = self.tcx.param_env(def_id);
                     if !param_env.can_type_implement_copy(self.tcx, ty).is_ok() {
-                        emit_feature_err(&self.tcx.sess.parse_sess,
-                                         sym::untagged_unions, item.span, GateIssue::Language,
-                                         "unions with non-`Copy` fields are unstable");
+                        feature_err(
+                            &self.tcx.sess.parse_sess, sym::untagged_unions, item.span,
+                            "unions with non-`Copy` fields are unstable"
+                        )
+                        .emit();
                     }
                 }
             }
diff --git a/src/librustc/query/mod.rs b/src/librustc/query/mod.rs
index cd93fed8e1e..d715ddb1b81 100644
--- a/src/librustc/query/mod.rs
+++ b/src/librustc/query/mod.rs
@@ -1130,7 +1130,7 @@ rustc_queries! {
             desc { |tcx| "estimating size for `{}`", tcx.def_path_str(def.def_id()) }
         }
 
-        query features_query(_: CrateNum) -> &'tcx feature_gate::Features {
+        query features_query(_: CrateNum) -> &'tcx rustc_feature::Features {
             eval_always
             desc { "looking up enabled feature gates" }
         }
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 6733250e1e8..fbfae721bbe 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -7,6 +7,7 @@ use crate::session::{early_error, early_warn, Session};
 use crate::session::search_paths::SearchPath;
 
 use rustc_data_structures::fx::FxHashSet;
+use rustc_feature::UnstableFeatures;
 
 use rustc_target::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, RelroLevel};
 use rustc_target::spec::{Target, TargetTriple};
@@ -16,7 +17,6 @@ use syntax::ast;
 use syntax::source_map::{FileName, FilePathMapping};
 use syntax::edition::{Edition, EDITION_NAME_LIST, DEFAULT_EDITION};
 use syntax::symbol::{sym, Symbol};
-use syntax::feature_gate::UnstableFeatures;
 
 use errors::emitter::HumanReadableErrorType;
 use errors::{ColorConfig, FatalError, Handler};
@@ -2701,7 +2701,7 @@ pub fn parse_crate_types_from_list(list_list: Vec<String>) -> Result<Vec<CrateTy
 
 pub mod nightly_options {
     use getopts;
-    use syntax::feature_gate::UnstableFeatures;
+    use rustc_feature::UnstableFeatures;
     use super::{ErrorOutputType, OptionStability, RustcOptGroup};
     use crate::session::early_error;
 
@@ -2850,9 +2850,9 @@ mod dep_tracking {
     use super::{CrateType, DebugInfo, ErrorOutputType, OptLevel, OutputTypes,
                 Passes, Sanitizer, LtoCli, LinkerPluginLto, SwitchWithOptPath,
                 SymbolManglingVersion};
+    use rustc_feature::UnstableFeatures;
     use rustc_target::spec::{MergeFunctions, PanicStrategy, RelroLevel, TargetTriple};
     use syntax::edition::Edition;
-    use syntax::feature_gate::UnstableFeatures;
 
     pub trait DepTrackingHash {
         fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType);
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 0b9d04ca6a3..87c7e5a82a3 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -21,7 +21,6 @@ use errors::emitter::{Emitter, EmitterWriter};
 use errors::emitter::HumanReadableErrorType;
 use errors::annotate_snippet_emitter_writer::{AnnotateSnippetEmitterWriter};
 use syntax::edition::Edition;
-use syntax::feature_gate;
 use errors::json::JsonEmitter;
 use syntax::source_map;
 use syntax::sess::ParseSess;
@@ -86,7 +85,7 @@ pub struct Session {
     /// `rustc_codegen_llvm::back::symbol_names` module for more information.
     pub crate_disambiguator: Once<CrateDisambiguator>,
 
-    features: Once<feature_gate::Features>,
+    features: Once<rustc_feature::Features>,
 
     /// The maximum recursion limit for potentially infinitely recursive
     /// operations such as auto-dereference and monomorphization.
@@ -470,11 +469,11 @@ impl Session {
     /// DO NOT USE THIS METHOD if there is a TyCtxt available, as it circumvents
     /// dependency tracking. Use tcx.features() instead.
     #[inline]
-    pub fn features_untracked(&self) -> &feature_gate::Features {
+    pub fn features_untracked(&self) -> &rustc_feature::Features {
         self.features.get()
     }
 
-    pub fn init_features(&self, features: feature_gate::Features) {
+    pub fn init_features(&self, features: rustc_feature::Features) {
         self.features.set(features);
     }
 
diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs
index 0b6937975aa..776ae7dc141 100644
--- a/src/librustc/ty/context.rs
+++ b/src/librustc/ty/context.rs
@@ -72,7 +72,6 @@ use rustc_macros::HashStable;
 use syntax::ast;
 use syntax::attr;
 use syntax::source_map::MultiSpan;
-use syntax::feature_gate;
 use syntax::symbol::{Symbol, kw, sym};
 use syntax_pos::Span;
 use syntax::expand::allocator::AllocatorKind;
@@ -1312,7 +1311,7 @@ impl<'tcx> TyCtxt<'tcx> {
         self.cstore.allocator_kind()
     }
 
-    pub fn features(self) -> &'tcx feature_gate::Features {
+    pub fn features(self) -> &'tcx rustc_feature::Features {
         self.features_query(LOCAL_CRATE)
     }
 
diff --git a/src/librustc/ty/query/mod.rs b/src/librustc/ty/query/mod.rs
index a1eb1c43335..5b4a6ac8a2d 100644
--- a/src/librustc/ty/query/mod.rs
+++ b/src/librustc/ty/query/mod.rs
@@ -56,7 +56,6 @@ use std::any::type_name;
 use syntax_pos::{Span, DUMMY_SP};
 use syntax::attr;
 use syntax::ast;
-use syntax::feature_gate;
 use syntax::symbol::Symbol;
 
 #[macro_use]
diff --git a/src/librustc_codegen_llvm/lib.rs b/src/librustc_codegen_llvm/lib.rs
index e7562c399b2..acc221f0657 100644
--- a/src/librustc_codegen_llvm/lib.rs
+++ b/src/librustc_codegen_llvm/lib.rs
@@ -30,6 +30,7 @@ extern crate libc;
 #[macro_use] extern crate rustc;
 extern crate rustc_target;
 #[macro_use] extern crate rustc_data_structures;
+extern crate rustc_feature;
 extern crate rustc_index;
 extern crate rustc_incremental;
 extern crate rustc_codegen_utils;
diff --git a/src/librustc_codegen_llvm/llvm_util.rs b/src/librustc_codegen_llvm/llvm_util.rs
index 290ca409261..7bff9e69dd5 100644
--- a/src/librustc_codegen_llvm/llvm_util.rs
+++ b/src/librustc_codegen_llvm/llvm_util.rs
@@ -6,7 +6,7 @@ use rustc::session::config::PrintRequest;
 use rustc_target::spec::{MergeFunctions, PanicStrategy};
 use libc::c_int;
 use std::ffi::CString;
-use syntax::feature_gate::UnstableFeatures;
+use rustc_feature::UnstableFeatures;
 use syntax::symbol::sym;
 
 use std::str;
diff --git a/src/librustc_driver/Cargo.toml b/src/librustc_driver/Cargo.toml
index 2b7e4d35248..d1cb4cbeb9b 100644
--- a/src/librustc_driver/Cargo.toml
+++ b/src/librustc_driver/Cargo.toml
@@ -19,6 +19,7 @@ rustc_target = { path = "../librustc_target" }
 rustc_lint = { path = "../librustc_lint" }
 rustc_data_structures = { path = "../librustc_data_structures" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_metadata = { path = "../librustc_metadata" }
 rustc_mir = { path = "../librustc_mir" }
 rustc_parse = { path = "../librustc_parse" }
diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs
index c945de8f1e1..8b04d3d46d0 100644
--- a/src/librustc_driver/lib.rs
+++ b/src/librustc_driver/lib.rs
@@ -44,7 +44,7 @@ use errors::{PResult, registry::Registry};
 use rustc_interface::interface;
 use rustc_interface::util::get_codegen_sysroot;
 use rustc_data_structures::sync::SeqCst;
-
+use rustc_feature::{find_gated_cfg, UnstableFeatures};
 use rustc_serialize::json::ToJson;
 
 use std::borrow::Cow;
@@ -61,10 +61,9 @@ use std::str;
 use std::time::Instant;
 
 use syntax::ast;
-use syntax::source_map::FileLoader;
-use syntax::feature_gate::{GatedCfg, UnstableFeatures};
-use syntax::symbol::sym;
-use syntax_pos::{DUMMY_SP, FileName};
+use syntax_pos::source_map::FileLoader;
+use syntax_pos::symbol::sym;
+use syntax_pos::FileName;
 
 pub mod pretty;
 mod args;
@@ -684,12 +683,6 @@ impl RustcDefaultCalls {
                         .is_nightly_build();
 
                     let mut cfgs = sess.parse_sess.config.iter().filter_map(|&(name, ref value)| {
-                        let gated_cfg = GatedCfg::gate(&ast::MetaItem {
-                            path: ast::Path::from_ident(ast::Ident::with_dummy_span(name)),
-                            kind: ast::MetaItemKind::Word,
-                            span: DUMMY_SP,
-                        });
-
                         // Note that crt-static is a specially recognized cfg
                         // directive that's printed out here as part of
                         // rust-lang/rust#37406, but in general the
@@ -700,10 +693,11 @@ impl RustcDefaultCalls {
                         // through to build scripts.
                         let value = value.as_ref().map(|s| s.as_str());
                         let value = value.as_ref().map(|s| s.as_ref());
-                        if name != sym::target_feature || value != Some("crt-static") {
-                            if !allow_unstable_cfg && gated_cfg.is_some() {
-                                return None
-                            }
+                        if (name != sym::target_feature || value != Some("crt-static"))
+                            && !allow_unstable_cfg
+                            && find_gated_cfg(|cfg_sym| cfg_sym == name).is_some()
+                        {
+                            return None;
                         }
 
                         if let Some(value) = value {
diff --git a/src/librustc_feature/Cargo.toml b/src/librustc_feature/Cargo.toml
new file mode 100644
index 00000000000..40ce922947b
--- /dev/null
+++ b/src/librustc_feature/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+authors = ["The Rust Project Developers"]
+name = "rustc_feature"
+version = "0.0.0"
+edition = "2018"
+
+[lib]
+name = "rustc_feature"
+path = "lib.rs"
+doctest = false
+
+[dependencies]
+rustc_data_structures = { path = "../librustc_data_structures" }
+lazy_static = "1.0.0"
+syntax_pos = { path = "../libsyntax_pos" }
diff --git a/src/libsyntax/feature_gate/accepted.rs b/src/librustc_feature/accepted.rs
index dab83f48a03..fec5a7f1a45 100644
--- a/src/libsyntax/feature_gate/accepted.rs
+++ b/src/librustc_feature/accepted.rs
@@ -1,7 +1,7 @@
 //! List of the accepted feature gates.
 
-use crate::symbol::sym;
 use super::{State, Feature};
+use syntax_pos::symbol::sym;
 
 macro_rules! declare_features {
     ($(
diff --git a/src/libsyntax/feature_gate/active.rs b/src/librustc_feature/active.rs
index b04b30aa6bc..7c0d39965fc 100644
--- a/src/libsyntax/feature_gate/active.rs
+++ b/src/librustc_feature/active.rs
@@ -2,10 +2,9 @@
 
 use super::{State, Feature};
 
-use crate::edition::Edition;
-use crate::symbol::{Symbol, sym};
-
+use syntax_pos::edition::Edition;
 use syntax_pos::Span;
+use syntax_pos::symbol::{Symbol, sym};
 
 macro_rules! set {
     ($field: ident) => {{
@@ -37,7 +36,7 @@ macro_rules! declare_features {
             ),+];
 
         /// A set of features to be used by later passes.
-        #[derive(Clone)]
+        #[derive(Clone, Default)]
         pub struct Features {
             /// `#![feature]` attrs for language features, for error reporting.
             pub declared_lang_features: Vec<(Symbol, Span, Option<Symbol>)>,
@@ -50,17 +49,7 @@ macro_rules! declare_features {
         }
 
         impl Features {
-            pub fn new() -> Features {
-                Features {
-                    declared_lang_features: Vec::new(),
-                    declared_lib_features: Vec::new(),
-                    $($feature: false),+
-                }
-            }
-
-            pub fn walk_feature_fields<F>(&self, mut f: F)
-                where F: FnMut(&str, bool)
-            {
+            pub fn walk_feature_fields(&self, mut f: impl FnMut(&str, bool)) {
                 $(f(stringify!($feature), self.$feature);)+
             }
         }
diff --git a/src/libsyntax/feature_gate/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs
index a9f41633f30..f72df00a8e8 100644
--- a/src/libsyntax/feature_gate/builtin_attrs.rs
+++ b/src/librustc_feature/builtin_attrs.rs
@@ -3,17 +3,10 @@
 use AttributeType::*;
 use AttributeGate::*;
 
-use super::check::{emit_feature_err, GateIssue};
-use super::check::{Stability, EXPLAIN_ALLOW_INTERNAL_UNSAFE, EXPLAIN_ALLOW_INTERNAL_UNSTABLE};
-use super::active::Features;
+use crate::{Features, Stability};
 
-use crate::ast;
-use crate::attr::AttributeTemplate;
-use crate::sess::ParseSess;
-use crate::symbol::{Symbol, sym};
-
-use syntax_pos::Span;
 use rustc_data_structures::fx::FxHashMap;
+use syntax_pos::symbol::{Symbol, sym};
 use lazy_static::lazy_static;
 
 type GateFn = fn(&Features) -> bool;
@@ -24,39 +17,19 @@ macro_rules! cfg_fn {
     }
 }
 
+pub type GatedCfg = (Symbol, Symbol, GateFn);
+
 /// `cfg(...)`'s that are feature gated.
-const GATED_CFGS: &[(Symbol, Symbol, GateFn)] = &[
+const GATED_CFGS: &[GatedCfg] = &[
     // (name in cfg, feature, function to check if the feature is enabled)
     (sym::target_thread_local, sym::cfg_target_thread_local, cfg_fn!(cfg_target_thread_local)),
     (sym::target_has_atomic, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)),
     (sym::target_has_atomic_load_store, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)),
 ];
 
-#[derive(Debug)]
-pub struct GatedCfg {
-    span: Span,
-    index: usize,
-}
-
-impl GatedCfg {
-    pub fn gate(cfg: &ast::MetaItem) -> Option<GatedCfg> {
-        GATED_CFGS.iter()
-                  .position(|info| cfg.check_name(info.0))
-                  .map(|idx| {
-                      GatedCfg {
-                          span: cfg.span,
-                          index: idx
-                      }
-                  })
-    }
-
-    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(feature) {
-            let explain = format!("`cfg({})` is experimental and subject to change", cfg);
-            emit_feature_err(sess, feature, self.span, GateIssue::Language, &explain);
-        }
-    }
+/// Find a gated cfg determined by the `pred`icate which is given the cfg's name.
+pub fn find_gated_cfg(pred: impl Fn(Symbol) -> bool) -> Option<&'static GatedCfg> {
+    GATED_CFGS.iter().find(|(cfg_sym, ..)| pred(*cfg_sym))
 }
 
 // If you change this, please modify `src/doc/unstable-book` as well. You must
@@ -108,6 +81,21 @@ impl AttributeGate {
     }
 }
 
+/// A template that the attribute input must match.
+/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
+#[derive(Clone, Copy)]
+pub struct AttributeTemplate {
+    pub word: bool,
+    pub list: Option<&'static str>,
+    pub name_value_str: Option<&'static str>,
+}
+
+impl AttributeTemplate {
+    pub fn only_word() -> Self {
+        Self { word: true, list: None, name_value_str: None }
+    }
+}
+
 /// A convenience macro for constructing attribute templates.
 /// E.g., `template!(Word, List: "description")` means that the attribute
 /// supports forms `#[attr]` and `#[attr(description)]`.
@@ -361,9 +349,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
     gated!(
         allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."),
-        EXPLAIN_ALLOW_INTERNAL_UNSTABLE,
+        "allow_internal_unstable side-steps feature gating and stability checks",
+    ),
+    gated!(
+        allow_internal_unsafe, Normal, template!(Word),
+        "allow_internal_unsafe side-steps the unsafe_code lint",
     ),
-    gated!(allow_internal_unsafe, Normal, template!(Word), EXPLAIN_ALLOW_INTERNAL_UNSAFE),
 
     // ==========================================================================
     // Internal attributes: Type system related:
@@ -587,14 +578,10 @@ pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> {
     BUILTIN_ATTRIBUTES.iter().filter(|(.., gate)| gate.is_deprecated()).collect()
 }
 
-pub fn is_builtin_attr_name(name: ast::Name) -> bool {
+pub fn is_builtin_attr_name(name: Symbol) -> bool {
     BUILTIN_ATTRIBUTE_MAP.get(&name).is_some()
 }
 
-pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
-    attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)).is_some()
-}
-
 lazy_static! {
     pub static ref BUILTIN_ATTRIBUTE_MAP: FxHashMap<Symbol, &'static BuiltinAttribute> = {
         let mut map = FxHashMap::default();
diff --git a/src/librustc_feature/lib.rs b/src/librustc_feature/lib.rs
new file mode 100644
index 00000000000..c38bb3740af
--- /dev/null
+++ b/src/librustc_feature/lib.rs
@@ -0,0 +1,137 @@
+//! # Feature gates
+//!
+//! This crate declares the set of past and present unstable features in the compiler.
+//! Feature gate checking itself is done in `libsyntax/feature_gate/check.rs` at the moment.
+//!
+//! Features are enabled in programs via the crate-level attributes of
+//! `#![feature(...)]` with a comma-separated list of features.
+//!
+//! For the purpose of future feature-tracking, once a feature gate is added,
+//! even if it is stabilized or removed, *do not remove it*. Instead, move the
+//! symbol to the `accepted` or `removed` modules respectively.
+
+mod accepted;
+mod removed;
+mod active;
+mod builtin_attrs;
+
+use std::fmt;
+use std::num::NonZeroU32;
+use syntax_pos::{Span, edition::Edition, symbol::Symbol};
+
+#[derive(Clone, Copy)]
+pub enum State {
+    Accepted,
+    Active { set: fn(&mut Features, Span) },
+    Removed { reason: Option<&'static str> },
+    Stabilized { reason: Option<&'static str> },
+}
+
+impl fmt::Debug for State {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            State::Accepted { .. } => write!(f, "accepted"),
+            State::Active { .. } => write!(f, "active"),
+            State::Removed { .. } => write!(f, "removed"),
+            State::Stabilized { .. } => write!(f, "stabilized"),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Feature {
+    pub state: State,
+    pub name: Symbol,
+    pub since: &'static str,
+    issue: Option<u32>,  // FIXME: once #58732 is done make this an Option<NonZeroU32>
+    pub edition: Option<Edition>,
+    description: &'static str,
+}
+
+impl Feature {
+    fn issue(&self) -> Option<NonZeroU32> {
+        self.issue.and_then(|i| NonZeroU32::new(i))
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum Stability {
+    Unstable,
+    // First argument is tracking issue link; second argument is an optional
+    // help message, which defaults to "remove this attribute".
+    Deprecated(&'static str, Option<&'static str>),
+}
+
+#[derive(Clone, Copy, Hash)]
+pub enum UnstableFeatures {
+    /// Hard errors for unstable features are active, as on beta/stable channels.
+    Disallow,
+    /// Allow features to be activated, as on nightly.
+    Allow,
+    /// Errors are bypassed for bootstrapping. This is required any time
+    /// during the build that feature-related lints are set to warn or above
+    /// because the build turns on warnings-as-errors and uses lots of unstable
+    /// features. As a result, this is always required for building Rust itself.
+    Cheat
+}
+
+impl UnstableFeatures {
+    pub fn from_environment() -> UnstableFeatures {
+        // `true` if this is a feature-staged build, i.e., on the beta or stable channel.
+        let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some();
+        // `true` if we should enable unstable features for bootstrapping.
+        let bootstrap = std::env::var("RUSTC_BOOTSTRAP").is_ok();
+        match (disable_unstable_features, bootstrap) {
+            (_, true) => UnstableFeatures::Cheat,
+            (true, _) => UnstableFeatures::Disallow,
+            (false, _) => UnstableFeatures::Allow
+        }
+    }
+
+    pub fn is_nightly_build(&self) -> bool {
+        match *self {
+            UnstableFeatures::Allow | UnstableFeatures::Cheat => true,
+            UnstableFeatures::Disallow => false,
+        }
+    }
+}
+
+fn find_lang_feature_issue(feature: Symbol) -> Option<NonZeroU32> {
+    if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.name == feature) {
+        // FIXME (#28244): enforce that active features have issue numbers
+        // assert!(info.issue().is_some())
+        info.issue()
+    } else {
+        // search in Accepted, Removed, or Stable Removed features
+        let found = ACCEPTED_FEATURES
+            .iter()
+            .chain(REMOVED_FEATURES)
+            .chain(STABLE_REMOVED_FEATURES)
+            .find(|t| t.name == feature);
+        match found {
+            Some(found) => found.issue(),
+            None => panic!("feature `{}` is not declared anywhere", feature),
+        }
+    }
+}
+
+pub enum GateIssue {
+    Language,
+    Library(Option<NonZeroU32>)
+}
+
+pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option<NonZeroU32> {
+    match issue {
+        GateIssue::Language => find_lang_feature_issue(feature),
+        GateIssue::Library(lib) => lib,
+    }
+}
+
+pub use accepted::ACCEPTED_FEATURES;
+pub use active::{ACTIVE_FEATURES, Features, INCOMPLETE_FEATURES};
+pub use removed::{REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
+pub use builtin_attrs::{
+    AttributeGate, AttributeTemplate, AttributeType, find_gated_cfg, GatedCfg,
+    BuiltinAttribute, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
+    deprecated_attributes, is_builtin_attr_name,
+};
diff --git a/src/libsyntax/feature_gate/removed.rs b/src/librustc_feature/removed.rs
index f0aa74c65df..340bd32fb8a 100644
--- a/src/libsyntax/feature_gate/removed.rs
+++ b/src/librustc_feature/removed.rs
@@ -1,7 +1,7 @@
 //! List of the removed feature gates.
 
-use crate::symbol::sym;
 use super::{State, Feature};
+use syntax_pos::symbol::sym;
 
 macro_rules! declare_features {
     ($(
diff --git a/src/librustc_lint/Cargo.toml b/src/librustc_lint/Cargo.toml
index d26d8791f66..ed38243581b 100644
--- a/src/librustc_lint/Cargo.toml
+++ b/src/librustc_lint/Cargo.toml
@@ -15,5 +15,6 @@ rustc_target = { path = "../librustc_target" }
 syntax = { path = "../libsyntax" }
 syntax_pos = { path = "../libsyntax_pos" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_index = { path = "../librustc_index" }
 rustc_error_codes = { path = "../librustc_error_codes" }
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 38624034022..5d3a6cccc4e 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -34,15 +34,15 @@ use lint::{LateContext, LintContext, LintArray};
 use lint::{LintPass, LateLintPass, EarlyLintPass, EarlyContext};
 
 use rustc::util::nodemap::FxHashSet;
+use rustc_feature::{AttributeGate, AttributeTemplate, AttributeType, deprecated_attributes};
+use rustc_feature::Stability;
 
 use syntax::tokenstream::{TokenTree, TokenStream};
 use syntax::ast::{self, Expr};
 use syntax::ptr::P;
-use syntax::attr::{self, HasAttrs, AttributeTemplate};
+use syntax::attr::{self, HasAttrs};
 use syntax::source_map::Spanned;
 use syntax::edition::Edition;
-use syntax::feature_gate::{self, AttributeGate, AttributeType};
-use syntax::feature_gate::{Stability, deprecated_attributes};
 use syntax_pos::{BytePos, Span};
 use syntax::symbol::{Symbol, kw, sym};
 use syntax::errors::{Applicability, DiagnosticBuilder};
@@ -1850,7 +1850,7 @@ impl EarlyLintPass for IncompleteFeatures {
         features.declared_lang_features
             .iter().map(|(name, span, _)| (name, span))
             .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
-            .filter(|(name, _)| feature_gate::INCOMPLETE_FEATURES.iter().any(|f| name == &f))
+            .filter(|(name, _)| rustc_feature::INCOMPLETE_FEATURES.iter().any(|f| name == &f))
             .for_each(|(name, &span)| {
                 cx.struct_span_lint(
                     INCOMPLETE_FEATURES,
diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs
index 9f293bdaa10..f7de7ec7e18 100644
--- a/src/librustc_lint/unused.rs
+++ b/src/librustc_lint/unused.rs
@@ -1,3 +1,4 @@
+use rustc::hir;
 use rustc::hir::def::{Res, DefKind};
 use rustc::hir::def_id::DefId;
 use rustc::lint;
@@ -7,19 +8,17 @@ use rustc::ty::adjustment;
 use rustc_data_structures::fx::FxHashMap;
 use lint::{LateContext, EarlyContext, LintContext, LintArray};
 use lint::{LintPass, EarlyLintPass, LateLintPass};
+use rustc_feature::{AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 
 use syntax::ast;
 use syntax::attr;
 use syntax::errors::{Applicability, pluralize};
-use syntax::feature_gate::{AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 use syntax::print::pprust;
 use syntax::symbol::{kw, sym};
 use syntax::symbol::Symbol;
 use syntax::util::parser;
 use syntax_pos::{Span, BytePos};
 
-use rustc::hir;
-
 use log::debug;
 
 declare_lint! {
diff --git a/src/librustc_metadata/native_libs.rs b/src/librustc_metadata/native_libs.rs
index e577b238c86..425e5d1d821 100644
--- a/src/librustc_metadata/native_libs.rs
+++ b/src/librustc_metadata/native_libs.rs
@@ -7,7 +7,7 @@ use rustc::util::nodemap::FxHashSet;
 use rustc_target::spec::abi::Abi;
 use syntax::attr;
 use syntax::source_map::Span;
-use syntax::feature_gate::{self, GateIssue};
+use syntax::feature_gate::feature_err;
 use syntax::symbol::{kw, sym, Symbol};
 use syntax::{span_err, struct_span_err};
 
@@ -158,27 +158,29 @@ impl Collector<'tcx> {
             }
         }
         if lib.cfg.is_some() && !self.tcx.features().link_cfg {
-            feature_gate::emit_feature_err(&self.tcx.sess.parse_sess,
-                                           sym::link_cfg,
-                                           span.unwrap(),
-                                           GateIssue::Language,
-                                           "is unstable");
+            feature_err(&self.tcx.sess.parse_sess, sym::link_cfg, span.unwrap(), "is unstable")
+                .emit();
         }
         if lib.kind == cstore::NativeStaticNobundle &&
-           !self.tcx.features().static_nobundle {
-            feature_gate::emit_feature_err(&self.tcx.sess.parse_sess,
-                                           sym::static_nobundle,
-                                           span.unwrap_or_else(|| syntax_pos::DUMMY_SP),
-                                           GateIssue::Language,
-                                           "kind=\"static-nobundle\" is unstable");
+           !self.tcx.features().static_nobundle
+        {
+            feature_err(
+                &self.tcx.sess.parse_sess,
+                sym::static_nobundle,
+                span.unwrap_or_else(|| syntax_pos::DUMMY_SP),
+                "kind=\"static-nobundle\" is unstable"
+            )
+            .emit();
         }
         if lib.kind == cstore::NativeRawDylib &&
            !self.tcx.features().raw_dylib {
-            feature_gate::emit_feature_err(&self.tcx.sess.parse_sess,
-                                           sym::raw_dylib,
-                                           span.unwrap_or_else(|| syntax_pos::DUMMY_SP),
-                                           GateIssue::Language,
-                                           "kind=\"raw-dylib\" is unstable");
+            feature_err(
+                &self.tcx.sess.parse_sess,
+                sym::raw_dylib,
+                span.unwrap_or_else(|| syntax_pos::DUMMY_SP),
+                "kind=\"raw-dylib\" is unstable"
+            )
+            .emit();
         }
         self.libs.push(lib);
     }
diff --git a/src/librustc_mir/transform/check_consts/ops.rs b/src/librustc_mir/transform/check_consts/ops.rs
index acad56be604..a4f12a4e54f 100644
--- a/src/librustc_mir/transform/check_consts/ops.rs
+++ b/src/librustc_mir/transform/check_consts/ops.rs
@@ -4,7 +4,7 @@ use rustc::hir::def_id::DefId;
 use rustc::mir::BorrowKind;
 use rustc::session::config::nightly_options;
 use rustc::ty::TyCtxt;
-use syntax::feature_gate::{emit_feature_err, GateIssue};
+use syntax::feature_gate::feature_err;
 use syntax::symbol::sym;
 use syntax_pos::{Span, Symbol};
 
@@ -222,13 +222,13 @@ impl NonConstOp for Panic {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
+        feature_err(
             &item.tcx.sess.parse_sess,
             sym::const_panic,
             span,
-            GateIssue::Language,
             &format!("panicking in {}s is unstable", item.const_kind()),
-        );
+        )
+        .emit();
     }
 }
 
@@ -240,13 +240,13 @@ impl NonConstOp for RawPtrComparison {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
+        feature_err(
             &item.tcx.sess.parse_sess,
             sym::const_compare_raw_pointers,
             span,
-            GateIssue::Language,
             &format!("comparing raw pointers inside {}", item.const_kind()),
-        );
+        )
+        .emit();
     }
 }
 
@@ -258,14 +258,14 @@ impl NonConstOp for RawPtrDeref {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
-            &item.tcx.sess.parse_sess, sym::const_raw_ptr_deref,
-            span, GateIssue::Language,
+        feature_err(
+            &item.tcx.sess.parse_sess, sym::const_raw_ptr_deref, span,
             &format!(
                 "dereferencing raw pointers in {}s is unstable",
                 item.const_kind(),
             ),
-        );
+        )
+        .emit();
     }
 }
 
@@ -277,14 +277,14 @@ impl NonConstOp for RawPtrToIntCast {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
-            &item.tcx.sess.parse_sess, sym::const_raw_ptr_to_usize_cast,
-            span, GateIssue::Language,
+        feature_err(
+            &item.tcx.sess.parse_sess, sym::const_raw_ptr_to_usize_cast, span,
             &format!(
                 "casting pointers to integers in {}s is unstable",
                 item.const_kind(),
             ),
-        );
+        )
+        .emit();
     }
 }
 
@@ -334,11 +334,11 @@ impl NonConstOp for Transmute {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
-            &item.tcx.sess.parse_sess, sym::const_transmute,
-            span, GateIssue::Language,
-            &format!("The use of std::mem::transmute() \
-            is gated in {}s", item.const_kind()));
+        feature_err(
+            &item.tcx.sess.parse_sess, sym::const_transmute, span,
+            &format!("The use of std::mem::transmute() is gated in {}s", item.const_kind())
+        )
+        .emit();
     }
 }
 
@@ -355,10 +355,10 @@ impl NonConstOp for UnionAccess {
     }
 
     fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
-        emit_feature_err(
-            &item.tcx.sess.parse_sess, sym::const_fn_union,
-            span, GateIssue::Language,
+        feature_err(
+            &item.tcx.sess.parse_sess, sym::const_fn_union, span,
             "unions in const fn are unstable",
-        );
+        )
+        .emit();
     }
 }
diff --git a/src/librustc_parse/Cargo.toml b/src/librustc_parse/Cargo.toml
index a9175487a75..95b3256f53a 100644
--- a/src/librustc_parse/Cargo.toml
+++ b/src/librustc_parse/Cargo.toml
@@ -16,6 +16,7 @@ syntax_pos = { path = "../libsyntax_pos" }
 syntax = { path = "../libsyntax" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_lexer = { path = "../librustc_lexer" }
 rustc_target = { path = "../librustc_target" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
diff --git a/src/librustc_parse/config.rs b/src/librustc_parse/config.rs
index 7ce555ed57a..26e51e83d62 100644
--- a/src/librustc_parse/config.rs
+++ b/src/librustc_parse/config.rs
@@ -9,14 +9,9 @@
 //! [#64197]: https://github.com/rust-lang/rust/issues/64197
 
 use crate::validate_attr;
+use rustc_feature::Features;
 use syntax::attr::HasAttrs;
-use syntax::feature_gate::{
-    feature_err,
-    EXPLAIN_STMT_ATTR_SYNTAX,
-    Features,
-    get_features,
-    GateIssue,
-};
+use syntax::feature_gate::{feature_err, get_features};
 use syntax::attr;
 use syntax::ast;
 use syntax::edition::Edition;
@@ -52,7 +47,7 @@ pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition,
         } else { // the entire crate is unconfigured
             krate.attrs = Vec::new();
             krate.module.items = Vec::new();
-            return (krate, Features::new());
+            return (krate, Features::default());
         }
 
         features = get_features(&sess.span_diagnostic, &krate.attrs, edition, allow_features);
@@ -217,8 +212,7 @@ impl<'a> StripUnconfigured<'a> {
             let mut err = feature_err(self.sess,
                                       sym::stmt_expr_attributes,
                                       attr.span,
-                                      GateIssue::Language,
-                                      EXPLAIN_STMT_ATTR_SYNTAX);
+                                      "attributes on expressions are experimental");
 
             if attr.is_doc_comment() {
                 err.help("`///` is for documentation comments. For a plain comment, use `//`.");
diff --git a/src/librustc_parse/validate_attr.rs b/src/librustc_parse/validate_attr.rs
index bbe0dc1c35f..a3c9e266593 100644
--- a/src/librustc_parse/validate_attr.rs
+++ b/src/librustc_parse/validate_attr.rs
@@ -1,10 +1,10 @@
 //! Meta-syntax validation logic of attributes for post-expansion.
 
 use errors::{PResult, Applicability};
-use syntax::ast::{self, Attribute, AttrKind, Ident, MetaItem};
-use syntax::attr::{AttributeTemplate, mk_name_value_item_str};
+use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP};
+use syntax::ast::{self, Attribute, AttrKind, Ident, MetaItem, MetaItemKind};
+use syntax::attr::mk_name_value_item_str;
 use syntax::early_buffered_lints::BufferedEarlyLintId;
-use syntax::feature_gate::BUILTIN_ATTRIBUTE_MAP;
 use syntax::token;
 use syntax::tokenstream::TokenTree;
 use syntax::sess::ParseSess;
@@ -41,6 +41,16 @@ pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Meta
     })
 }
 
+/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
+fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
+    match meta {
+        MetaItemKind::Word => template.word,
+        MetaItemKind::List(..) => template.list.is_some(),
+        MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
+        MetaItemKind::NameValue(..) => false,
+    }
+}
+
 pub fn check_builtin_attribute(
     sess: &ParseSess,
     attr: &Attribute,
@@ -57,7 +67,7 @@ pub fn check_builtin_attribute(
                              name == sym::test || name == sym::bench;
 
     match parse_meta(sess, attr) {
-        Ok(meta) => if !should_skip(name) && !template.compatible(&meta.kind) {
+        Ok(meta) => if !should_skip(name) && !is_attr_template_compatible(&template, &meta.kind) {
             let error_msg = format!("malformed `{}` attribute input", name);
             let mut msg = "attribute must be of the form ".to_owned();
             let mut suggestions = vec![];
diff --git a/src/librustc_passes/Cargo.toml b/src/librustc_passes/Cargo.toml
index 7a98734bdbe..bb2f7c67418 100644
--- a/src/librustc_passes/Cargo.toml
+++ b/src/librustc_passes/Cargo.toml
@@ -12,6 +12,7 @@ path = "lib.rs"
 log = "0.4"
 rustc = { path = "../librustc" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_index = { path = "../librustc_index" }
 rustc_parse = { path = "../librustc_parse" }
 rustc_target = { path = "../librustc_target" }
diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs
index e189b7175f9..5a29a56ad54 100644
--- a/src/librustc_passes/ast_validation.rs
+++ b/src/librustc_passes/ast_validation.rs
@@ -14,7 +14,6 @@ use rustc_parse::validate_attr;
 use syntax::ast::*;
 use syntax::attr;
 use syntax::expand::is_proc_macro_attr;
-use syntax::feature_gate::is_builtin_attr;
 use syntax::print::pprust;
 use syntax::source_map::Spanned;
 use syntax::symbol::{kw, sym};
@@ -257,7 +256,7 @@ impl<'a> AstValidator<'a> {
             .flat_map(|i| i.attrs.as_ref())
             .filter(|attr| {
                 let arr = [sym::allow, sym::cfg, sym::cfg_attr, sym::deny, sym::forbid, sym::warn];
-                !arr.contains(&attr.name_or_empty()) && is_builtin_attr(attr)
+                !arr.contains(&attr.name_or_empty()) && attr::is_builtin_attr(attr)
             })
             .for_each(|attr| if attr.is_doc_comment() {
                 let mut err = self.err_handler().struct_span_err(
diff --git a/src/librustc_passes/check_const.rs b/src/librustc_passes/check_const.rs
index 24bc088e24a..63c6e60de79 100644
--- a/src/librustc_passes/check_const.rs
+++ b/src/librustc_passes/check_const.rs
@@ -13,8 +13,9 @@ use rustc::hir::map::Map;
 use rustc::hir;
 use rustc::ty::TyCtxt;
 use rustc::ty::query::Providers;
+use rustc_feature::Features;
 use syntax::ast::Mutability;
-use syntax::feature_gate::{emit_feature_err, Features, GateIssue};
+use syntax::feature_gate::feature_err;
 use syntax::span_err;
 use syntax_pos::{sym, Span};
 use rustc_error_codes::*;
@@ -140,13 +141,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {
             | NonConstExpr::Match(hir::MatchSource::Normal)
             | NonConstExpr::Match(hir::MatchSource::IfDesugar { .. })
             | NonConstExpr::Match(hir::MatchSource::IfLetDesugar { .. })
-            => emit_feature_err(
-                &self.tcx.sess.parse_sess,
-                sym::const_if_match,
-                span,
-                GateIssue::Language,
-                &msg
-            ),
+            => feature_err(&self.tcx.sess.parse_sess, sym::const_if_match, span, &msg).emit(),
 
             _ => span_err!(self.tcx.sess, span, E0744, "{}", msg),
         }
diff --git a/src/librustc_resolve/Cargo.toml b/src/librustc_resolve/Cargo.toml
index 6cce893f8ec..caca20e4221 100644
--- a/src/librustc_resolve/Cargo.toml
+++ b/src/librustc_resolve/Cargo.toml
@@ -20,6 +20,7 @@ arena = { path = "../libarena" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
 syntax_pos = { path = "../libsyntax_pos" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_metadata = { path = "../librustc_metadata" }
 rustc_error_codes = { path = "../librustc_error_codes" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs
index eb7265cb9cc..04e233c5973 100644
--- a/src/librustc_resolve/build_reduced_graph.rs
+++ b/src/librustc_resolve/build_reduced_graph.rs
@@ -29,10 +29,8 @@ use errors::Applicability;
 
 use syntax::ast::{Name, Ident};
 use syntax::attr;
-
 use syntax::ast::{self, Block, ForeignItem, ForeignItemKind, Item, ItemKind, NodeId};
 use syntax::ast::{MetaItemKind, StmtKind, TraitItem, TraitItemKind};
-use syntax::feature_gate::is_builtin_attr;
 use syntax::token::{self, Token};
 use syntax::print::pprust;
 use syntax::{span_err, struct_span_err};
@@ -1231,7 +1229,7 @@ impl<'a, 'b> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b> {
     }
 
     fn visit_attribute(&mut self, attr: &'b ast::Attribute) {
-        if !attr.is_doc_comment() && is_builtin_attr(attr) {
+        if !attr.is_doc_comment() && attr::is_builtin_attr(attr) {
             self.r.builtin_attrs.push(
                 (attr.get_normal_item().path.segments[0].ident, self.parent_scope)
             );
diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs
index e134b8b92ac..4dcafb6d279 100644
--- a/src/librustc_resolve/diagnostics.rs
+++ b/src/librustc_resolve/diagnostics.rs
@@ -9,8 +9,8 @@ use rustc::hir::def_id::{CRATE_DEF_INDEX, DefId};
 use rustc::session::Session;
 use rustc::ty::{self, DefIdTree};
 use rustc::util::nodemap::FxHashSet;
+use rustc_feature::BUILTIN_ATTRIBUTES;
 use syntax::ast::{self, Ident, Path};
-use syntax::feature_gate::BUILTIN_ATTRIBUTES;
 use syntax::source_map::SourceMap;
 use syntax::struct_span_err;
 use syntax::symbol::{Symbol, kw};
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 21c24f9da1c..9e7098da49f 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -12,18 +12,18 @@ use rustc::middle::stability;
 use rustc::session::Session;
 use rustc::util::nodemap::FxHashSet;
 use rustc::{ty, lint, span_bug};
+use rustc_feature::is_builtin_attr_name;
 use syntax::ast::{self, NodeId, Ident};
 use syntax::attr::{self, StabilityLevel};
 use syntax::edition::Edition;
-use syntax::feature_gate::{emit_feature_err, is_builtin_attr_name};
-use syntax::feature_gate::GateIssue;
+use syntax::feature_gate::feature_err;
 use syntax::print::pprust;
-use syntax::symbol::{Symbol, kw, sym};
 use syntax_expand::base::{self, InvocationRes, Indeterminate};
 use syntax_expand::base::SyntaxExtension;
 use syntax_expand::expand::{AstFragment, AstFragmentKind, Invocation, InvocationKind};
 use syntax_expand::compile_declarative_macro;
 use syntax_pos::hygiene::{self, ExpnId, ExpnData, ExpnKind};
+use syntax_pos::symbol::{Symbol, kw, sym};
 use syntax_pos::{Span, DUMMY_SP};
 
 use std::{mem, ptr};
@@ -346,13 +346,8 @@ impl<'a> Resolver<'a> {
                segment.ident.as_str().starts_with("rustc") {
                 let msg =
                     "attributes starting with `rustc` are reserved for use by the `rustc` compiler";
-                emit_feature_err(
-                    &self.session.parse_sess,
-                    sym::rustc_attrs,
-                    segment.ident.span,
-                    GateIssue::Language,
-                    msg,
-                );
+                feature_err(&self.session.parse_sess, sym::rustc_attrs, segment.ident.span, msg)
+                    .emit();
             }
         }
 
diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs
index fdbf729a773..af978d5095e 100644
--- a/src/librustc_typeck/astconv.rs
+++ b/src/librustc_typeck/astconv.rs
@@ -23,7 +23,7 @@ use crate::require_c_abi_if_c_variadic;
 use smallvec::SmallVec;
 use syntax::ast;
 use syntax::errors::pluralize;
-use syntax::feature_gate::{GateIssue, emit_feature_err};
+use syntax::feature_gate::feature_err;
 use syntax::util::lev_distance::find_best_match_for_name;
 use syntax::symbol::sym;
 use syntax_pos::{DUMMY_SP, Span, MultiSpan};
@@ -914,8 +914,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
             } else {
                 "parenthetical notation is only stable when used with `Fn`-family traits"
             };
-            emit_feature_err(&self.tcx().sess.parse_sess, sym::unboxed_closures,
-                             span, GateIssue::Language, msg);
+            feature_err(&self.tcx().sess.parse_sess, sym::unboxed_closures, span, msg).emit();
         }
 
         self.create_substs_for_ast_path(span,
diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs
index 871acb2726a..901a2192e20 100644
--- a/src/librustc_typeck/check/coercion.rs
+++ b/src/librustc_typeck/check/coercion.rs
@@ -644,11 +644,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
         }
 
         if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion {
-            feature_gate::emit_feature_err(&self.tcx.sess.parse_sess,
-                                           sym::unsized_tuple_coercion,
-                                           self.cause.span,
-                                           feature_gate::GateIssue::Language,
-                                           feature_gate::EXPLAIN_UNSIZED_TUPLE_COERCION);
+            feature_gate::feature_err(
+                &self.tcx.sess.parse_sess,
+                sym::unsized_tuple_coercion,
+                self.cause.span,
+                "unsized tuple coercion is not stable enough for use and is subject to change",
+            )
+            .emit();
         }
 
         Ok(coercion)
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index 3a4a4a50bf2..c7a0190a1d1 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -125,7 +125,7 @@ use syntax_pos::{self, BytePos, Span, MultiSpan};
 use syntax_pos::hygiene::DesugaringKind;
 use syntax::ast;
 use syntax::attr;
-use syntax::feature_gate::{GateIssue, emit_feature_err};
+use syntax::feature_gate::feature_err;
 use syntax::source_map::{DUMMY_SP, original_sp};
 use syntax::symbol::{kw, sym, Ident};
 use syntax::util::parser::ExprPrecedence;
@@ -2373,13 +2373,13 @@ fn check_transparent(tcx: TyCtxt<'_>, sp: Span, def_id: DefId) {
 
     if adt.is_enum() {
         if !tcx.features().transparent_enums {
-            emit_feature_err(
+            feature_err(
                 &tcx.sess.parse_sess,
                 sym::transparent_enums,
                 sp,
-                GateIssue::Language,
                 "transparent enums are unstable",
-            );
+            )
+            .emit();
         }
         if adt.variants.len() != 1 {
             bad_variant_count(tcx, adt, sp, def_id);
@@ -2391,11 +2391,13 @@ fn check_transparent(tcx: TyCtxt<'_>, sp: Span, def_id: DefId) {
     }
 
     if adt.is_union() && !tcx.features().transparent_unions {
-        emit_feature_err(&tcx.sess.parse_sess,
-                         sym::transparent_unions,
-                         sp,
-                         GateIssue::Language,
-                         "transparent unions are unstable");
+        feature_err(
+            &tcx.sess.parse_sess,
+            sym::transparent_unions,
+            sp,
+            "transparent unions are unstable",
+        )
+        .emit();
     }
 
     // For each field, figure out if it's known to be a ZST and align(1)
@@ -2452,11 +2454,13 @@ pub fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, vs: &'tcx [hir::Variant], i
     let repr_type_ty = def.repr.discr_type().to_ty(tcx);
     if repr_type_ty == tcx.types.i128 || repr_type_ty == tcx.types.u128 {
         if !tcx.features().repr128 {
-            emit_feature_err(&tcx.sess.parse_sess,
-                             sym::repr128,
-                             sp,
-                             GateIssue::Language,
-                             "repr with 128-bit type is unstable");
+            feature_err(
+                &tcx.sess.parse_sess,
+                sym::repr128,
+                sp,
+                "repr with 128-bit type is unstable",
+            )
+            .emit();
         }
     }
 
diff --git a/src/librustc_typeck/check/wfcheck.rs b/src/librustc_typeck/check/wfcheck.rs
index b491b103313..20b6b01de57 100644
--- a/src/librustc_typeck/check/wfcheck.rs
+++ b/src/librustc_typeck/check/wfcheck.rs
@@ -10,7 +10,7 @@ use rustc::middle::lang_items;
 use rustc::infer::opaque_types::may_define_opaque_type;
 
 use syntax::ast;
-use syntax::feature_gate::{self, GateIssue};
+use syntax::feature_gate;
 use syntax_pos::Span;
 use syntax::symbol::sym;
 use errors::DiagnosticBuilder;
@@ -830,13 +830,13 @@ fn check_method_receiver<'fcx, 'tcx>(
                     &fcx.tcx.sess.parse_sess,
                     sym::arbitrary_self_types,
                     span,
-                    GateIssue::Language,
                     &format!(
                         "`{}` cannot be used as the type of `self` without \
                             the `arbitrary_self_types` feature",
                         receiver_ty,
                     ),
-                ).help(HELP_FOR_SELF_TYPE)
+                )
+                .help(HELP_FOR_SELF_TYPE)
                 .emit();
             } else {
                 // Report error; would not have worked with `arbitrary_self_types`.
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index 301b0ff3503..6d6e7685fa0 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -1492,16 +1492,16 @@ fn type_of(tcx: TyCtxt<'_>, def_id: DefId) -> Ty<'_> {
                         _ => None,
                     };
                     if let Some(unsupported_type) = err {
-                        feature_gate::emit_feature_err(
+                        feature_gate::feature_err(
                             &tcx.sess.parse_sess,
                             sym::const_compare_raw_pointers,
                             hir_ty.span,
-                            feature_gate::GateIssue::Language,
                             &format!(
                                 "using {} as const generic parameters is unstable",
                                 unsupported_type
                             ),
-                        );
+                        )
+                        .emit();
                     };
                 }
                 if ty::search_for_structural_match_violation(
@@ -2520,13 +2520,13 @@ fn from_target_feature(
                 None => true,
             };
             if !allowed && id.is_local() {
-                feature_gate::emit_feature_err(
+                feature_gate::feature_err(
                     &tcx.sess.parse_sess,
                     feature_gate.unwrap(),
                     item.span(),
-                    feature_gate::GateIssue::Language,
                     &format!("the target feature `{}` is currently unstable", feature),
-                );
+                )
+                .emit();
             }
             Some(Symbol::intern(feature))
         }));
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index 09f4873967e..078948cc63b 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -7,10 +7,10 @@ use std::mem;
 use std::fmt::{self, Write};
 use std::ops;
 
+use rustc_feature::Features;
 use syntax::symbol::{Symbol, sym};
 use syntax::ast::{MetaItem, MetaItemKind, NestedMetaItem, LitKind};
 use syntax::sess::ParseSess;
-use syntax::feature_gate::Features;
 
 use syntax_pos::Span;
 
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 7d1f89079f8..b77b1c720cf 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -12,12 +12,12 @@ use rustc::session::DiagnosticOutput;
 use rustc::util::nodemap::{FxHashMap, FxHashSet};
 use rustc_interface::interface;
 use rustc_driver::abort_on_err;
+use rustc_feature::UnstableFeatures;
 use rustc_resolve as resolve;
 
 use syntax::ast::CRATE_NODE_ID;
 use syntax::source_map;
 use syntax::attr;
-use syntax::feature_gate::UnstableFeatures;
 use errors::json::JsonEmitter;
 use syntax::symbol::sym;
 use syntax_pos::DUMMY_SP;
diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs
index 56f1191feed..7945850ef08 100644
--- a/src/librustdoc/externalfiles.rs
+++ b/src/librustdoc/externalfiles.rs
@@ -2,7 +2,7 @@ use std::fs;
 use std::path::Path;
 use std::str;
 use errors;
-use crate::syntax::feature_gate::UnstableFeatures;
+use rustc_feature::UnstableFeatures;
 use crate::syntax::edition::Edition;
 use crate::html::markdown::{IdMap, ErrorCodes, Markdown, Playground};
 
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index ba94cb82c00..b5c1a77a387 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -45,7 +45,6 @@ use errors;
 use serialize::json::{ToJson, Json, as_json};
 use syntax::ast;
 use syntax::edition::Edition;
-use syntax::feature_gate::UnstableFeatures;
 use syntax::print::pprust;
 use syntax::source_map::FileName;
 use syntax::symbol::{Symbol, sym};
@@ -56,6 +55,7 @@ use rustc::middle::stability;
 use rustc::hir;
 use rustc::util::nodemap::{FxHashMap, FxHashSet};
 use rustc_data_structures::flock;
+use rustc_feature::UnstableFeatures;
 
 use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, Mutability};
 use crate::config::RenderOptions;
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 69d9748bb88..be3644ecf96 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -24,6 +24,7 @@ extern crate env_logger;
 extern crate rustc;
 extern crate rustc_data_structures;
 extern crate rustc_driver;
+extern crate rustc_feature;
 extern crate rustc_error_codes;
 extern crate rustc_index;
 extern crate rustc_resolve;
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 8431271e62d..7dc3df23a6d 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -6,7 +6,7 @@ use errors;
 use testing;
 use syntax::edition::Edition;
 use syntax::source_map::DUMMY_SP;
-use syntax::feature_gate::UnstableFeatures;
+use rustc_feature::UnstableFeatures;
 
 use crate::externalfiles::{LoadStringError, load_string};
 use crate::config::{Options, RenderOptions};
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index d8f2dbca835..3c021ae7465 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -5,10 +5,10 @@ use rustc::hir;
 use rustc::lint as lint;
 use rustc::ty;
 use rustc_resolve::ParentScope;
+use rustc_feature::UnstableFeatures;
 use syntax;
 use syntax::ast::{self, Ident};
 use syntax_expand::base::SyntaxExtensionKind;
-use syntax::feature_gate::UnstableFeatures;
 use syntax::symbol::Symbol;
 use syntax_pos::DUMMY_SP;
 
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index d09eb0b2fc2..5fd7ab03224 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -1,4 +1,5 @@
 use rustc_data_structures::sync::Lrc;
+use rustc_feature::UnstableFeatures;
 use rustc_interface::interface;
 use rustc_target::spec::TargetTriple;
 use rustc::hir;
@@ -9,7 +10,6 @@ use syntax::ast;
 use syntax::with_globals;
 use syntax::source_map::SourceMap;
 use syntax::edition::Edition;
-use syntax::feature_gate::UnstableFeatures;
 use std::env;
 use std::io::{self, Write};
 use std::panic;
diff --git a/src/libsyntax/Cargo.toml b/src/libsyntax/Cargo.toml
index dff23076c82..085c1760c80 100644
--- a/src/libsyntax/Cargo.toml
+++ b/src/libsyntax/Cargo.toml
@@ -18,6 +18,7 @@ lazy_static = "1.0.0"
 syntax_pos = { path = "../libsyntax_pos" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_index = { path = "../librustc_index" }
 rustc_lexer = { path = "../librustc_lexer" }
 rustc_macros = { path = "../librustc_macros" }
diff --git a/src/libsyntax/attr/builtin.rs b/src/libsyntax/attr/builtin.rs
index c10541c8c7e..3c10f27b60a 100644
--- a/src/libsyntax/attr/builtin.rs
+++ b/src/libsyntax/attr/builtin.rs
@@ -1,7 +1,8 @@
 //! Parsing and validation of builtin attributes
 
+use super::{mark_used, MetaItemKind};
 use crate::ast::{self, Attribute, MetaItem, NestedMetaItem};
-use crate::feature_gate::{Features, GatedCfg};
+use crate::feature_gate::feature_err;
 use crate::print::pprust;
 use crate::sess::ParseSess;
 
@@ -9,12 +10,15 @@ use errors::{Applicability, Handler};
 use std::num::NonZeroU32;
 use syntax_pos::hygiene::Transparency;
 use syntax_pos::{symbol::Symbol, symbol::sym, Span};
+use rustc_feature::{Features, find_gated_cfg, GatedCfg, is_builtin_attr_name};
 use rustc_macros::HashStable_Generic;
 
-use super::{mark_used, MetaItemKind};
-
 use rustc_error_codes::*;
 
+pub fn is_builtin_attr(attr: &Attribute) -> bool {
+    attr.ident().filter(|ident| is_builtin_attr_name(ident.name)).is_some()
+}
+
 enum AttrError {
     MultipleItem(String),
     UnknownMetaItem(String, &'static [&'static str]),
@@ -24,31 +28,6 @@ enum AttrError {
     UnsupportedLiteral(&'static str, /* is_bytestr */ bool),
 }
 
-/// A template that the attribute input must match.
-/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
-#[derive(Clone, Copy)]
-pub struct AttributeTemplate {
-    pub word: bool,
-    pub list: Option<&'static str>,
-    pub name_value_str: Option<&'static str>,
-}
-
-impl AttributeTemplate {
-    pub fn only_word() -> Self {
-        Self { word: true, list: None, name_value_str: None }
-    }
-
-    /// Checks that the given meta-item is compatible with this template.
-    pub fn compatible(&self, meta_item_kind: &ast::MetaItemKind) -> bool {
-        match meta_item_kind {
-            ast::MetaItemKind::Word => self.word,
-            ast::MetaItemKind::List(..) => self.list.is_some(),
-            ast::MetaItemKind::NameValue(lit) if lit.kind.is_str() => self.name_value_str.is_some(),
-            ast::MetaItemKind::NameValue(..) => false,
-        }
-    }
-}
-
 fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) {
     let diag = &sess.span_diagnostic;
     match error {
@@ -555,8 +534,9 @@ pub fn find_crate_name(attrs: &[Attribute]) -> Option<Symbol> {
 /// Tests if a cfg-pattern matches the cfg set
 pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Features>) -> bool {
     eval_condition(cfg, sess, &mut |cfg| {
-        if let (Some(feats), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) {
-            gated_cfg.check_and_emit(sess, feats);
+        let gate = find_gated_cfg(|sym| cfg.check_name(sym));
+        if let (Some(feats), Some(gated_cfg)) = (features, gate) {
+            gate_cfg(&gated_cfg, cfg.span, sess, feats);
         }
         let error = |span, msg| { sess.span_diagnostic.span_err(span, msg); true };
         if cfg.path.segments.len() != 1 {
@@ -585,12 +565,21 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
     })
 }
 
+fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &Features) {
+    let (cfg, feature, has_feature) = gated_cfg;
+    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
+        let explain = format!("`cfg({})` is experimental and subject to change", cfg);
+        feature_err(sess, *feature, cfg_span, &explain).emit()
+    }
+}
+
 /// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
 /// evaluate individual items.
-pub fn eval_condition<F>(cfg: &ast::MetaItem, sess: &ParseSess, eval: &mut F)
-                         -> bool
-    where F: FnMut(&ast::MetaItem) -> bool
-{
+pub fn eval_condition(
+    cfg: &ast::MetaItem,
+    sess: &ParseSess,
+    eval: &mut impl FnMut(&ast::MetaItem) -> bool,
+) -> bool {
     match cfg.kind {
         ast::MetaItemKind::List(ref mis) => {
             for mi in mis.iter() {
diff --git a/src/libsyntax/feature_gate/check.rs b/src/libsyntax/feature_gate/check.rs
index ec0eaa56812..3d2c3b1d4f9 100644
--- a/src/libsyntax/feature_gate/check.rs
+++ b/src/libsyntax/feature_gate/check.rs
@@ -1,7 +1,7 @@
-use super::{active::{ACTIVE_FEATURES, Features}, Feature, State as FeatureState};
-use super::accepted::ACCEPTED_FEATURES;
-use super::removed::{REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
-use super::builtin_attrs::{AttributeGate, BUILTIN_ATTRIBUTE_MAP};
+use rustc_feature::{ACCEPTED_FEATURES, ACTIVE_FEATURES, REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
+use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP};
+use rustc_feature::{Features, Feature, State as FeatureState, UnstableFeatures};
+use rustc_feature::{find_feature_issue, GateIssue};
 
 use crate::ast::{self, AssocTyConstraint, AssocTyConstraintKind, NodeId};
 use crate::ast::{GenericParam, GenericParamKind, PatKind, RangeEnd, VariantData};
@@ -19,18 +19,6 @@ use log::debug;
 
 use rustc_error_codes::*;
 
-
-use std::env;
-use std::num::NonZeroU32;
-
-#[derive(Copy, Clone, Debug)]
-pub enum Stability {
-    Unstable,
-    // First argument is tracking issue link; second argument is an optional
-    // help message, which defaults to "remove this attribute"
-    Deprecated(&'static str, Option<&'static str>),
-}
-
 macro_rules! gate_feature_fn {
     ($cx: expr, $has_feature: expr, $span: expr, $name: expr, $explain: expr, $level: expr) => {{
         let (cx, has_feature, span,
@@ -59,30 +47,6 @@ pub fn check_attribute(attr: &ast::Attribute, parse_sess: &ParseSess, features:
     PostExpansionVisitor { parse_sess, features }.visit_attribute(attr)
 }
 
-fn find_lang_feature_issue(feature: Symbol) -> Option<NonZeroU32> {
-    if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.name == feature) {
-        // FIXME (#28244): enforce that active features have issue numbers
-        // assert!(info.issue().is_some())
-        info.issue()
-    } else {
-        // search in Accepted, Removed, or Stable Removed features
-        let found = ACCEPTED_FEATURES
-            .iter()
-            .chain(REMOVED_FEATURES)
-            .chain(STABLE_REMOVED_FEATURES)
-            .find(|t| t.name == feature);
-        match found {
-            Some(found) => found.issue(),
-            None => panic!("feature `{}` is not declared anywhere", feature),
-        }
-    }
-}
-
-pub enum GateIssue {
-    Language,
-    Library(Option<NonZeroU32>)
-}
-
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum GateStrength {
     /// A hard error. (Most feature gates should use this.)
@@ -91,41 +55,35 @@ pub enum GateStrength {
     Soft,
 }
 
-pub fn emit_feature_err(
-    sess: &ParseSess,
+pub fn feature_err<'a>(
+    sess: &'a ParseSess,
     feature: Symbol,
-    span: Span,
-    issue: GateIssue,
+    span: impl Into<MultiSpan>,
     explain: &str,
-) {
-    feature_err(sess, feature, span, issue, explain).emit();
+) -> DiagnosticBuilder<'a> {
+    feature_err_issue(sess, feature, span, GateIssue::Language, explain)
 }
 
-pub fn feature_err<'a, S: Into<MultiSpan>>(
+pub fn feature_err_issue<'a>(
     sess: &'a ParseSess,
     feature: Symbol,
-    span: S,
+    span: impl Into<MultiSpan>,
     issue: GateIssue,
     explain: &str,
 ) -> DiagnosticBuilder<'a> {
     leveled_feature_err(sess, feature, span, issue, explain, GateStrength::Hard)
 }
 
-fn leveled_feature_err<'a, S: Into<MultiSpan>>(
+fn leveled_feature_err<'a>(
     sess: &'a ParseSess,
     feature: Symbol,
-    span: S,
+    span: impl Into<MultiSpan>,
     issue: GateIssue,
     explain: &str,
     level: GateStrength,
 ) -> DiagnosticBuilder<'a> {
     let diag = &sess.span_diagnostic;
 
-    let issue = match issue {
-        GateIssue::Language => find_lang_feature_issue(feature),
-        GateIssue::Library(lib) => lib,
-    };
-
     let mut err = match level {
         GateStrength::Hard => {
             diag.struct_span_err_with_code(span, explain, stringify_error_code!(E0658))
@@ -133,7 +91,7 @@ fn leveled_feature_err<'a, S: Into<MultiSpan>>(
         GateStrength::Soft => diag.struct_span_warn(span, explain),
     };
 
-    if let Some(n) = issue {
+    if let Some(n) = find_feature_issue(feature, issue) {
         err.note(&format!(
             "for more information, see https://github.com/rust-lang/rust/issues/{}",
             n,
@@ -156,20 +114,6 @@ fn leveled_feature_err<'a, S: Into<MultiSpan>>(
 
 }
 
-const EXPLAIN_BOX_SYNTAX: &str =
-    "box expression syntax is experimental; you can call `Box::new` instead";
-
-pub const EXPLAIN_STMT_ATTR_SYNTAX: &str =
-    "attributes on expressions are experimental";
-
-pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &str =
-    "allow_internal_unstable side-steps feature gating and stability checks";
-pub const EXPLAIN_ALLOW_INTERNAL_UNSAFE: &str =
-    "allow_internal_unsafe side-steps the unsafe_code lint";
-
-pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &str =
-    "unsized tuple coercion is not stable enough for use and is subject to change";
-
 struct PostExpansionVisitor<'a> {
     parse_sess: &'a ParseSess,
     features: &'a Features,
@@ -282,7 +226,6 @@ impl<'a> PostExpansionVisitor<'a> {
                 self.parse_sess,
                 sym::arbitrary_enum_discriminant,
                 discriminant_spans.clone(),
-                crate::feature_gate::GateIssue::Language,
                 "custom discriminant values are not allowed in enums with tuple or struct variants",
             );
             for sp in discriminant_spans {
@@ -529,7 +472,10 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
     fn visit_expr(&mut self, e: &'a ast::Expr) {
         match e.kind {
             ast::ExprKind::Box(_) => {
-                gate_feature_post!(&self, box_syntax, e.span, EXPLAIN_BOX_SYNTAX);
+                gate_feature_post!(
+                    &self, box_syntax, e.span,
+                    "box expression syntax is experimental; you can call `Box::new` instead"
+                );
             }
             ast::ExprKind::Type(..) => {
                 // To avoid noise about type ascription in common syntax errors, only emit if it
@@ -695,7 +641,7 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute],
         err.emit();
     }
 
-    let mut features = Features::new();
+    let mut features = Features::default();
     let mut edition_enabled_features = FxHashMap::default();
 
     for &edition in ALL_EDITIONS {
@@ -900,40 +846,6 @@ pub fn check_crate(krate: &ast::Crate,
     visit::walk_crate(&mut visitor, krate);
 }
 
-#[derive(Clone, Copy, Hash)]
-pub enum UnstableFeatures {
-    /// Hard errors for unstable features are active, as on beta/stable channels.
-    Disallow,
-    /// Allow features to be activated, as on nightly.
-    Allow,
-    /// Errors are bypassed for bootstrapping. This is required any time
-    /// during the build that feature-related lints are set to warn or above
-    /// because the build turns on warnings-as-errors and uses lots of unstable
-    /// features. As a result, this is always required for building Rust itself.
-    Cheat
-}
-
-impl UnstableFeatures {
-    pub fn from_environment() -> UnstableFeatures {
-        // `true` if this is a feature-staged build, i.e., on the beta or stable channel.
-        let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some();
-        // `true` if we should enable unstable features for bootstrapping.
-        let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok();
-        match (disable_unstable_features, bootstrap) {
-            (_, true) => UnstableFeatures::Cheat,
-            (true, _) => UnstableFeatures::Disallow,
-            (false, _) => UnstableFeatures::Allow
-        }
-    }
-
-    pub fn is_nightly_build(&self) -> bool {
-        match *self {
-            UnstableFeatures::Allow | UnstableFeatures::Cheat => true,
-            UnstableFeatures::Disallow => false,
-        }
-    }
-}
-
 fn maybe_stage_features(span_handler: &Handler, krate: &ast::Crate, unstable: UnstableFeatures) {
     if !unstable.is_nightly_build() {
         for attr in krate.attrs.iter().filter(|attr| attr.check_name(sym::feature)) {
diff --git a/src/libsyntax/feature_gate/mod.rs b/src/libsyntax/feature_gate/mod.rs
deleted file mode 100644
index c4418c0f0f6..00000000000
--- a/src/libsyntax/feature_gate/mod.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-//! # Feature gating
-//!
-//! This module implements the gating necessary for preventing certain compiler
-//! features from being used by default. This module will crawl a pre-expanded
-//! AST to ensure that there are no features which are used that are not
-//! enabled.
-//!
-//! Features are enabled in programs via the crate-level attributes of
-//! `#![feature(...)]` with a comma-separated list of features.
-//!
-//! For the purpose of future feature-tracking, once code for detection of feature
-//! gate usage is added, *do not remove it again* even once the feature
-//! becomes stable.
-
-mod accepted;
-mod removed;
-mod active;
-mod builtin_attrs;
-mod check;
-
-use crate::{edition::Edition, symbol::Symbol};
-use std::fmt;
-use std::num::NonZeroU32;
-use syntax_pos::Span;
-
-#[derive(Clone, Copy)]
-pub enum State {
-    Accepted,
-    Active { set: fn(&mut Features, Span) },
-    Removed { reason: Option<&'static str> },
-    Stabilized { reason: Option<&'static str> },
-}
-
-impl fmt::Debug for State {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            State::Accepted { .. } => write!(f, "accepted"),
-            State::Active { .. } => write!(f, "active"),
-            State::Removed { .. } => write!(f, "removed"),
-            State::Stabilized { .. } => write!(f, "stabilized"),
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Feature {
-    state: State,
-    name: Symbol,
-    since: &'static str,
-    issue: Option<u32>,  // FIXME: once #58732 is done make this an Option<NonZeroU32>
-    edition: Option<Edition>,
-    description: &'static str,
-}
-
-impl Feature {
-    fn issue(&self) -> Option<NonZeroU32> {
-        self.issue.and_then(|i| NonZeroU32::new(i))
-    }
-}
-
-pub use active::{Features, INCOMPLETE_FEATURES};
-pub use builtin_attrs::{
-    AttributeGate, AttributeType, GatedCfg,
-    BuiltinAttribute, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
-    deprecated_attributes, is_builtin_attr,  is_builtin_attr_name,
-};
-pub use check::{
-    check_crate, check_attribute, get_features, feature_err, emit_feature_err,
-    Stability, GateIssue, UnstableFeatures,
-    EXPLAIN_STMT_ATTR_SYNTAX, EXPLAIN_UNSIZED_TUPLE_COERCION,
-};
diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs
index 22b49862f49..3d4a5d624c1 100644
--- a/src/libsyntax/lib.rs
+++ b/src/libsyntax/lib.rs
@@ -92,7 +92,10 @@ pub mod attr;
 pub mod expand;
 pub use syntax_pos::source_map;
 pub mod entry;
-pub mod feature_gate;
+pub mod feature_gate {
+    mod check;
+    pub use check::{check_crate, check_attribute, get_features, feature_err, feature_err_issue};
+}
 pub mod mut_visit;
 pub mod ptr;
 pub mod show_span;
diff --git a/src/libsyntax/sess.rs b/src/libsyntax/sess.rs
index 740e9dfe459..aa9217c1b69 100644
--- a/src/libsyntax/sess.rs
+++ b/src/libsyntax/sess.rs
@@ -3,15 +3,15 @@
 
 use crate::ast::{CrateConfig, NodeId};
 use crate::early_buffered_lints::{BufferedEarlyLint, BufferedEarlyLintId};
-use crate::source_map::{SourceMap, FilePathMapping};
-use crate::feature_gate::UnstableFeatures;
 
 use errors::{Applicability, emitter::SilentEmitter, Handler, ColorConfig, DiagnosticBuilder};
 use rustc_data_structures::fx::{FxHashSet, FxHashMap};
 use rustc_data_structures::sync::{Lrc, Lock, Once};
+use rustc_feature::UnstableFeatures;
 use syntax_pos::{Symbol, Span, MultiSpan};
 use syntax_pos::edition::Edition;
 use syntax_pos::hygiene::ExpnId;
+use syntax_pos::source_map::{SourceMap, FilePathMapping};
 
 use std::path::PathBuf;
 use std::str;
diff --git a/src/libsyntax_expand/Cargo.toml b/src/libsyntax_expand/Cargo.toml
index 653b87f0d82..897d5a65ba3 100644
--- a/src/libsyntax_expand/Cargo.toml
+++ b/src/libsyntax_expand/Cargo.toml
@@ -16,6 +16,7 @@ log = "0.4"
 syntax_pos = { path = "../libsyntax_pos" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_lexer = { path = "../librustc_lexer" }
 rustc_parse = { path = "../librustc_parse" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
diff --git a/src/libsyntax_expand/expand.rs b/src/libsyntax_expand/expand.rs
index 4f05b0147bf..a6ced1439c5 100644
--- a/src/libsyntax_expand/expand.rs
+++ b/src/libsyntax_expand/expand.rs
@@ -4,16 +4,17 @@ use crate::hygiene::{ExpnId, SyntaxContext, ExpnData, ExpnKind};
 use crate::mbe::macro_rules::annotate_err_with_kind;
 use crate::placeholders::{placeholder, PlaceholderExpander};
 use crate::config::StripUnconfigured;
-use rustc_parse::configure;
 
+use rustc_feature::Features;
+use rustc_parse::configure;
 use rustc_parse::DirectoryOwnership;
 use rustc_parse::parser::Parser;
 use rustc_parse::validate_attr;
 use syntax::ast::{self, AttrItem, Block, Ident, LitKind, NodeId, PatKind, Path};
 use syntax::ast::{MacStmtStyle, StmtKind, ItemKind};
-use syntax::attr::{self, HasAttrs};
+use syntax::attr::{self, HasAttrs, is_builtin_attr};
 use syntax::source_map::respan;
-use syntax::feature_gate::{self, Features, GateIssue, is_builtin_attr, emit_feature_err};
+use syntax::feature_gate::{self, feature_err};
 use syntax::mut_visit::*;
 use syntax::print::pprust;
 use syntax::ptr::P;
@@ -726,13 +727,13 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         if self.cx.ecfg.proc_macro_hygiene() {
             return
         }
-        emit_feature_err(
+        feature_err(
             self.cx.parse_sess,
             sym::proc_macro_hygiene,
             span,
-            GateIssue::Language,
             &format!("custom attributes cannot be applied to {}", kind),
-        );
+        )
+        .emit();
     }
 
     fn gate_proc_macro_input(&self, annotatable: &Annotatable) {
@@ -744,13 +745,13 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             fn visit_item(&mut self, item: &'ast ast::Item) {
                 match &item.kind {
                     ast::ItemKind::Mod(module) if !module.inline => {
-                        emit_feature_err(
+                        feature_err(
                             self.parse_sess,
                             sym::proc_macro_hygiene,
                             item.span,
-                            GateIssue::Language,
                             "non-inline modules in proc macro input are unstable",
-                        );
+                        )
+                        .emit();
                     }
                     _ => {}
                 }
@@ -789,13 +790,13 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         if self.cx.ecfg.proc_macro_hygiene() {
             return
         }
-        emit_feature_err(
+        feature_err(
             self.cx.parse_sess,
             sym::proc_macro_hygiene,
             span,
-            GateIssue::Language,
             &format!("procedural macros cannot be expanded to {}", kind),
-        );
+        )
+        .emit();
     }
 
     fn parse_ast_fragment(
@@ -991,9 +992,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
         if let Some(attr) = &attr {
             if !self.cx.ecfg.custom_inner_attributes() &&
                attr.style == ast::AttrStyle::Inner && !attr.has_name(sym::test) {
-                emit_feature_err(&self.cx.parse_sess, sym::custom_inner_attributes,
-                                 attr.span, GateIssue::Language,
-                                 "non-builtin inner attributes are unstable");
+                feature_err(
+                    &self.cx.parse_sess, sym::custom_inner_attributes, attr.span,
+                    "non-builtin inner attributes are unstable"
+                )
+                .emit();
             }
         }
         attr
diff --git a/src/libsyntax_expand/mbe/macro_rules.rs b/src/libsyntax_expand/mbe/macro_rules.rs
index a1d8b5a5338..b191527df19 100644
--- a/src/libsyntax_expand/mbe/macro_rules.rs
+++ b/src/libsyntax_expand/mbe/macro_rules.rs
@@ -8,12 +8,12 @@ use crate::mbe::macro_parser::{Error, Failure, Success};
 use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, NamedParseResult};
 use crate::mbe::transcribe::transcribe;
 
+use rustc_feature::Features;
 use rustc_parse::parser::Parser;
 use rustc_parse::Directory;
 use syntax::ast;
 use syntax::attr::{self, TransparencyError};
 use syntax::edition::Edition;
-use syntax::feature_gate::Features;
 use syntax::print::pprust;
 use syntax::sess::ParseSess;
 use syntax::symbol::{kw, sym, Symbol};
diff --git a/src/libsyntax_ext/Cargo.toml b/src/libsyntax_ext/Cargo.toml
index 2ebdac8ef7c..d73a9ea6cdb 100644
--- a/src/libsyntax_ext/Cargo.toml
+++ b/src/libsyntax_ext/Cargo.toml
@@ -14,6 +14,7 @@ errors = { path = "../librustc_errors", package = "rustc_errors" }
 fmt_macros = { path = "../libfmt_macros" }
 log = "0.4"
 rustc_data_structures = { path = "../librustc_data_structures" }
+rustc_feature = { path = "../librustc_feature" }
 rustc_parse = { path = "../librustc_parse" }
 rustc_target = { path = "../librustc_target" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
diff --git a/src/libsyntax_ext/test_harness.rs b/src/libsyntax_ext/test_harness.rs
index 659780d7a43..4c1eec38c6e 100644
--- a/src/libsyntax_ext/test_harness.rs
+++ b/src/libsyntax_ext/test_harness.rs
@@ -2,13 +2,13 @@
 
 use log::debug;
 use smallvec::{smallvec, SmallVec};
+use rustc_feature::Features;
 use rustc_target::spec::PanicStrategy;
 use syntax::ast::{self, Ident};
 use syntax::attr;
 use syntax::entry::{self, EntryPointType};
 use syntax_expand::base::{ExtCtxt, Resolver};
 use syntax_expand::expand::{AstFragment, ExpansionConfig};
-use syntax::feature_gate::Features;
 use syntax::mut_visit::{*, ExpectOne};
 use syntax::ptr::P;
 use syntax::sess::ParseSess;
diff --git a/src/libsyntax_ext/util.rs b/src/libsyntax_ext/util.rs
index e59daab1770..f7bd9a05604 100644
--- a/src/libsyntax_ext/util.rs
+++ b/src/libsyntax_ext/util.rs
@@ -1,7 +1,7 @@
 use rustc_parse::validate_attr;
+use rustc_feature::AttributeTemplate;
 use syntax_pos::Symbol;
 use syntax::ast::MetaItem;
-use syntax::attr::AttributeTemplate;
 use syntax_expand::base::ExtCtxt;
 
 pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index defe85ef46a..4ea101296b7 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -231,7 +231,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features {
 }
 
 fn collect_lang_features_in(base: &Path, file: &str, bad: &mut bool) -> Features {
-    let path = base.join("libsyntax/feature_gate").join(file);
+    let path = base.join("librustc_feature").join(file);
     let contents = t!(fs::read_to_string(&path));
 
     // We allow rustc-internal features to omit a tracking issue.