about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-01-07 17:17:22 -0800
committerAlex Crichton <alex@alexcrichton.com>2015-01-07 17:17:22 -0800
commitf3b67afcab9293faf77aafeaccfdda19e5a08d38 (patch)
tree03a51fdf8128137581efbd2c07b0c75f5db3c1f8
parent8bf3ee7c5c44fa5aa0a1003e8fe61a0af1527927 (diff)
parentc27133e2ceba52a6f3ff1b9c4bedb901d1bfe251 (diff)
downloadrust-f3b67afcab9293faf77aafeaccfdda19e5a08d38.tar.gz
rust-f3b67afcab9293faf77aafeaccfdda19e5a08d38.zip
rollup merge of #20663: brson/feature-staging
This partially implements the feature staging described in the
[release channel RFC][rc]. It does not yet fully conform to the RFC as
written, but does accomplish its goals sufficiently for the 1.0 alpha
release.

It has three primary user-visible effects:

* On the nightly channel, use of unstable APIs generates a warning.
* On the beta channel, use of unstable APIs generates a warning.
* On the beta channel, use of feature gates generates a warning.

Code that does not trigger these warnings is considered 'stable',
modulo pre-1.0 bugs.

Disabling the warnings for unstable APIs continues to be done in the
existing (i.e. old) style, via `#[allow(...)]`, not that specified in
the RFC. I deem this marginally acceptable since any code that must do
this is not using the stable dialect of Rust.

Use of feature gates is itself gated with the new 'unstable_features'
lint, on nightly set to 'allow', and on beta 'warn'.

The attribute scheme used here corresponds to an older version of the
RFC, with the `#[staged_api]` crate attribute toggling the staging
behavior of the stability attributes, but the user impact is only
in-tree so I'm not concerned about having to make design changes later
(and I may ultimately prefer the scheme here after all, with the
`#[staged_api]` crate attribute).

Since the Rust codebase itself makes use of unstable features the
compiler and build system do a midly elaborate dance to allow it to
bootstrap while disobeying these lints (which would otherwise be
errors because Rust builds with `-D warnings`).

This patch includes one significant hack that causes a
regression. Because the `format_args!` macro emits calls to unstable
APIs it would trigger the lint.  I added a hack to the lint to make it
not trigger, but this in turn causes arguments to `println!` not to be
checked for feature gates. I don't presently understand macro
expansion well enough to fix. This is bug #20661.

Closes #16678

[rc]: https://github.com/rust-lang/rfcs/blob/master/text/0507-release-channels.md

Next steps are to disable the existing out-of-tree behavior for stability attributes, and convert the remaining system to be feature-based per the RFC. During the first beta cycle we will set these lints to 'forbid'.
-rwxr-xr-xconfigure12
-rw-r--r--mk/main.mk11
-rw-r--r--src/liballoc/lib.rs1
-rw-r--r--src/libarena/lib.rs1
-rw-r--r--src/libcollections/lib.rs1
-rw-r--r--src/libcore/lib.rs1
-rw-r--r--src/libflate/lib.rs1
-rw-r--r--src/libfmt_macros/lib.rs1
-rw-r--r--src/libgetopts/lib.rs1
-rw-r--r--src/libgraphviz/lib.rs1
-rw-r--r--src/liblibc/lib.rs1
-rw-r--r--src/liblog/lib.rs1
-rw-r--r--src/librand/lib.rs1
-rw-r--r--src/librbml/lib.rs1
-rw-r--r--src/libregex/lib.rs1
-rw-r--r--src/librustc/lib.rs1
-rw-r--r--src/librustc/lint/builtin.rs77
-rw-r--r--src/librustc/lint/context.rs46
-rw-r--r--src/librustc/lint/mod.rs3
-rw-r--r--src/librustc/metadata/csearch.rs13
-rw-r--r--src/librustc/middle/stability.rs16
-rw-r--r--src/librustc/session/config.rs21
-rw-r--r--src/librustc_back/lib.rs1
-rw-r--r--src/librustc_borrowck/lib.rs1
-rw-r--r--src/librustc_driver/lib.rs22
-rw-r--r--src/librustc_llvm/lib.rs1
-rw-r--r--src/librustc_resolve/lib.rs1
-rw-r--r--src/librustc_trans/lib.rs1
-rw-r--r--src/librustc_typeck/lib.rs1
-rw-r--r--src/librustdoc/core.rs4
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/librustdoc/test.rs3
-rw-r--r--src/libserialize/lib.rs1
-rw-r--r--src/libstd/lib.rs1
-rw-r--r--src/libsyntax/codemap.rs7
-rw-r--r--src/libsyntax/lib.rs1
-rw-r--r--src/libsyntax/std_inject.rs21
-rw-r--r--src/libterm/lib.rs1
-rw-r--r--src/libtest/lib.rs1
-rw-r--r--src/libunicode/lib.rs1
-rw-r--r--src/test/compile-fail/feature-gate-feature-gate.rs14
-rw-r--r--src/test/compile-fail/feature-gated-feature-in-macro-arg.rs8
-rw-r--r--src/test/pretty/issue-4264.pp1
43 files changed, 272 insertions, 34 deletions
diff --git a/configure b/configure
index ea9320c901b..61c737e0fd3 100755
--- a/configure
+++ b/configure
@@ -599,6 +599,18 @@ then
 fi
 putvar CFG_RELEASE_CHANNEL
 
+# A magic value that allows the compiler to use unstable features
+# during the bootstrap even when doing so would normally be an error
+# because of feature staging or because the build turns on
+# warnings-as-errors and unstable features default to warnings.  The
+# build has to match this key in an env var. Meant to be a mild
+# deterrent from users just turning on unstable features on the stable
+# channel.
+# Basing CFG_BOOTSTRAP_KEY on CFG_BOOTSTRAP_KEY lets it get picked up
+# during a Makefile reconfig.
+CFG_BOOTSTRAP_KEY="${CFG_BOOTSTRAP_KEY-`date +%N`}"
+putvar CFG_BOOTSTRAP_KEY
+
 step_msg "looking for build programs"
 
 probe_need CFG_PERL        perl
diff --git a/mk/main.mk b/mk/main.mk
index a97e68af59b..e892286f7fd 100644
--- a/mk/main.mk
+++ b/mk/main.mk
@@ -25,11 +25,13 @@ ifeq ($(CFG_RELEASE_CHANNEL),stable)
 CFG_RELEASE=$(CFG_RELEASE_NUM)
 # This is the string used in dist artifact file names, e.g. "0.12.0", "nightly"
 CFG_PACKAGE_VERS=$(CFG_RELEASE_NUM)
+CFG_DISABLE_UNSTABLE_FEATURES=1
 endif
 ifeq ($(CFG_RELEASE_CHANNEL),beta)
 # The beta channel is temporarily called 'alpha'
 CFG_RELEASE=$(CFG_RELEASE_NUM)-alpha$(CFG_BETA_CYCLE)
 CFG_PACKAGE_VERS=$(CFG_RELEASE_NUM)-alpha$(CFG_BETA_CYCLE)
+CFG_DISABLE_UNSTABLE_FEATURES=1
 endif
 ifeq ($(CFG_RELEASE_CHANNEL),nightly)
 CFG_RELEASE=$(CFG_RELEASE_NUM)-nightly
@@ -319,11 +321,20 @@ export CFG_VERSION_WIN
 export CFG_RELEASE
 export CFG_PACKAGE_NAME
 export CFG_BUILD
+export CFG_RELEASE_CHANNEL
 export CFG_LLVM_ROOT
 export CFG_PREFIX
 export CFG_LIBDIR
 export CFG_LIBDIR_RELATIVE
 export CFG_DISABLE_INJECT_STD_VERSION
+ifdef CFG_DISABLE_UNSTABLE_FEATURES
+CFG_INFO := $(info cfg: disabling unstable features (CFG_DISABLE_UNSTABLE_FEATURES))
+# Turn on feature-staging
+export CFG_DISABLE_UNSTABLE_FEATURES
+endif
+# Subvert unstable feature lints to do the self-build
+export CFG_BOOTSTRAP_KEY
+export RUSTC_BOOTSTRAP_KEY:=$(CFG_BOOTSTRAP_KEY)
 
 ######################################################################
 # Per-stage targets and runner
diff --git a/src/liballoc/lib.rs b/src/liballoc/lib.rs
index 72509d34f29..dc6037c3c0c 100644
--- a/src/liballoc/lib.rs
+++ b/src/liballoc/lib.rs
@@ -58,6 +58,7 @@
 
 #![crate_name = "alloc"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
diff --git a/src/libarena/lib.rs b/src/libarena/lib.rs
index 423c16bfee8..79e43b9cc64 100644
--- a/src/libarena/lib.rs
+++ b/src/libarena/lib.rs
@@ -21,6 +21,7 @@
 
 #![crate_name = "arena"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libcollections/lib.rs b/src/libcollections/lib.rs
index 84cd898a263..9c105d1148a 100644
--- a/src/libcollections/lib.rs
+++ b/src/libcollections/lib.rs
@@ -15,6 +15,7 @@
 
 #![crate_name = "collections"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs
index a7e3b61b0d4..95b3e59c2c3 100644
--- a/src/libcore/lib.rs
+++ b/src/libcore/lib.rs
@@ -49,6 +49,7 @@
 
 #![crate_name = "core"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
diff --git a/src/libflate/lib.rs b/src/libflate/lib.rs
index f38440d86c6..1896bdd182a 100644
--- a/src/libflate/lib.rs
+++ b/src/libflate/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "flate"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libfmt_macros/lib.rs b/src/libfmt_macros/lib.rs
index 47cc072a636..a703bfbb8fe 100644
--- a/src/libfmt_macros/lib.rs
+++ b/src/libfmt_macros/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "fmt_macros"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libgetopts/lib.rs b/src/libgetopts/lib.rs
index f50e24c6354..0dd6a430a88 100644
--- a/src/libgetopts/lib.rs
+++ b/src/libgetopts/lib.rs
@@ -79,6 +79,7 @@
 
 #![crate_name = "getopts"]
 #![experimental = "use the crates.io `getopts` library instead"]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs
index 83bad70e7b1..21a8ba03454 100644
--- a/src/libgraphviz/lib.rs
+++ b/src/libgraphviz/lib.rs
@@ -266,6 +266,7 @@
 
 #![crate_name = "graphviz"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/liblibc/lib.rs b/src/liblibc/lib.rs
index 347a958076d..32e635e445d 100644
--- a/src/liblibc/lib.rs
+++ b/src/liblibc/lib.rs
@@ -11,6 +11,7 @@
 #![crate_name = "libc"]
 #![crate_type = "rlib"]
 #![cfg_attr(not(feature = "cargo-build"), experimental)]
+#![cfg_attr(not(feature = "cargo-build"), staged_api)]
 #![no_std]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
diff --git a/src/liblog/lib.rs b/src/liblog/lib.rs
index 08b01e956e1..6bfbbb99fc7 100644
--- a/src/liblog/lib.rs
+++ b/src/liblog/lib.rs
@@ -157,6 +157,7 @@
 
 #![crate_name = "log"]
 #![experimental = "use the crates.io `log` library instead"]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librand/lib.rs b/src/librand/lib.rs
index ad2a4dbec4e..3dd0a1e32df 100644
--- a/src/librand/lib.rs
+++ b/src/librand/lib.rs
@@ -25,6 +25,7 @@
 
 #![no_std]
 #![experimental]
+#![staged_api]
 
 #[macro_use]
 extern crate core;
diff --git a/src/librbml/lib.rs b/src/librbml/lib.rs
index a66d1dd08c1..66f4f2321fd 100644
--- a/src/librbml/lib.rs
+++ b/src/librbml/lib.rs
@@ -17,6 +17,7 @@
 
 #![crate_name = "rbml"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libregex/lib.rs b/src/libregex/lib.rs
index c039abc9aff..bd376528a0e 100644
--- a/src/libregex/lib.rs
+++ b/src/libregex/lib.rs
@@ -17,6 +17,7 @@
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![experimental = "use the crates.io `regex` library instead"]
+#![staged_api]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
        html_root_url = "http://doc.rust-lang.org/nightly/",
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index a3a041c2497..5525874a614 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "rustc"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs
index 1af8e2f29eb..9d7e833e711 100644
--- a/src/librustc/lint/builtin.rs
+++ b/src/librustc/lint/builtin.rs
@@ -34,7 +34,7 @@ use middle::{def, pat_util, stability};
 use middle::const_eval::{eval_const_expr_partial, const_int, const_uint};
 use util::ppaux::{ty_to_string};
 use util::nodemap::{FnvHashMap, NodeSet};
-use lint::{Context, LintPass, LintArray};
+use lint::{Context, LintPass, LintArray, Lint};
 
 use std::collections::hash_map::Entry::{Occupied, Vacant};
 use std::num::SignedInt;
@@ -1643,6 +1643,13 @@ declare_lint! {
     "detects use of #[unstable] items (incl. items with no stability attribute)"
 }
 
+declare_lint!(STAGED_EXPERIMENTAL, Warn,
+              "detects use of #[experimental] items in staged builds");
+
+declare_lint!(STAGED_UNSTABLE, Warn,
+              "detects use of #[unstable] items (incl. items with no stability attribute) \
+               in staged builds");
+
 /// Checks for use of items with `#[deprecated]`, `#[experimental]` and
 /// `#[unstable]` attributes, or no stability attribute.
 #[derive(Copy)]
@@ -1650,12 +1657,13 @@ pub struct Stability;
 
 impl Stability {
     fn lint(&self, cx: &Context, id: ast::DefId, span: Span) {
-        let stability = stability::lookup(cx.tcx, id);
+
+        let ref stability = stability::lookup(cx.tcx, id);
         let cross_crate = !ast_util::is_local(id);
 
         // stability attributes are promises made across crates; only
         // check DEPRECATED for crate-local usage.
-        let (lint, label) = match stability {
+        let (lint, label) = match *stability {
             // no stability attributes == Unstable
             None if cross_crate => (UNSTABLE, "unmarked"),
             Some(attr::Stability { level: attr::Unstable, .. }) if cross_crate =>
@@ -1667,24 +1675,53 @@ impl Stability {
             _ => return
         };
 
-        let msg = match stability {
-            Some(attr::Stability { text: Some(ref s), .. }) => {
-                format!("use of {} item: {}", label, *s)
+        output(cx, span, stability, lint, label);
+        if cross_crate && stability::is_staged_api(cx.tcx, id) {
+            if lint.name == UNSTABLE.name {
+                output(cx, span, stability, STAGED_UNSTABLE, label);
+            } else if lint.name == EXPERIMENTAL.name {
+                output(cx, span, stability, STAGED_EXPERIMENTAL, label);
             }
-            _ => format!("use of {} item", label)
-        };
+        }
 
-        cx.span_lint(lint, span, msg.index(&FullRange));
+        fn output(cx: &Context, span: Span, stability: &Option<attr::Stability>,
+                  lint: &'static Lint, label: &'static str) {
+            let msg = match *stability {
+                Some(attr::Stability { text: Some(ref s), .. }) => {
+                    format!("use of {} item: {}", label, *s)
+                }
+                _ => format!("use of {} item", label)
+            };
+
+            cx.span_lint(lint, span, msg.index(&FullRange));
+        }
     }
 
+
     fn is_internal(&self, cx: &Context, span: Span) -> bool {
         cx.tcx.sess.codemap().span_is_internal(span)
     }
+
 }
 
 impl LintPass for Stability {
     fn get_lints(&self) -> LintArray {
-        lint_array!(DEPRECATED, EXPERIMENTAL, UNSTABLE)
+        lint_array!(DEPRECATED, EXPERIMENTAL, UNSTABLE, STAGED_EXPERIMENTAL, STAGED_UNSTABLE)
+    }
+
+    fn check_crate(&mut self, _: &Context, c: &ast::Crate) {
+        // Just mark the #[staged_api] attribute used, though nothing else is done
+        // with it during this pass over the source.
+        for attr in c.attrs.iter() {
+            if attr.name().get() == "staged_api" {
+                match attr.node.value.node {
+                    ast::MetaWord(_) => {
+                        attr::mark_used(attr);
+                    }
+                    _ => (/*pass*/)
+                }
+            }
+        }
     }
 
     fn check_view_item(&mut self, cx: &Context, item: &ast::ViewItem) {
@@ -1746,6 +1783,7 @@ impl LintPass for Stability {
             }
             _ => return
         };
+
         self.lint(cx, id, span);
     }
 
@@ -1878,3 +1916,22 @@ impl LintPass for HardwiredLints {
         )
     }
 }
+
+/// Forbids using the `#[feature(...)]` attribute
+#[deriving(Copy)]
+pub struct UnstableFeatures;
+
+declare_lint!(UNSTABLE_FEATURES, Allow,
+              "enabling unstable features");
+
+impl LintPass for UnstableFeatures {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(UNSTABLE_FEATURES)
+    }
+    fn check_attribute(&mut self, ctx: &Context, attr: &ast::Attribute) {
+        use syntax::attr;
+        if attr::contains_name(&[attr.node.value.clone()], "feature") {
+            ctx.span_lint(UNSTABLE_FEATURES, attr.span, "unstable feature");
+        }
+    }
+}
diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs
index 51998bdbcf2..7d58b04a7f4 100644
--- a/src/librustc/lint/context.rs
+++ b/src/librustc/lint/context.rs
@@ -28,8 +28,9 @@ use self::TargetLint::*;
 use middle::privacy::ExportedItems;
 use middle::ty::{self, Ty};
 use session::{early_error, Session};
+use session::config::UnstableFeatures;
 use lint::{Level, LevelSource, Lint, LintId, LintArray, LintPass, LintPassObject};
-use lint::{Default, CommandLine, Node, Allow, Warn, Deny, Forbid};
+use lint::{Default, CommandLine, Node, Allow, Warn, Deny, Forbid, ReleaseChannel};
 use lint::builtin;
 use util::nodemap::FnvHashMap;
 
@@ -210,6 +211,7 @@ impl LintStore {
                      UnusedAllocation,
                      Stability,
                      MissingCopyImplementations,
+                     UnstableFeatures,
         );
 
         add_builtin_with_new!(sess,
@@ -298,6 +300,29 @@ impl LintStore {
             }
         }
     }
+
+    fn maybe_stage_features(&mut self, sess: &Session) {
+        let lvl = match sess.opts.unstable_features {
+            UnstableFeatures::Default => return,
+            UnstableFeatures::Disallow => Warn,
+            UnstableFeatures::Cheat => Allow
+        };
+        match self.by_name.get("unstable_features") {
+            Some(&Id(lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            Some(&Renamed(_, lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            None => unreachable!()
+        }
+        match self.by_name.get("staged_unstable") {
+            Some(&Id(lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            Some(&Renamed(_, lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            None => unreachable!()
+        }
+        match self.by_name.get("staged_experimental") {
+            Some(&Id(lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            Some(&Renamed(_, lint_id)) => self.set_level(lint_id, (lvl, ReleaseChannel)),
+            None => unreachable!()
+        }
+    }
 }
 
 /// Context for lint checking.
@@ -380,6 +405,7 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint,
     if level == Allow { return }
 
     let name = lint.name_lower();
+    let mut def = None;
     let mut note = None;
     let msg = match source {
         Default => {
@@ -394,7 +420,13 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint,
                     }, name.replace("_", "-"))
         },
         Node(src) => {
-            note = Some(src);
+            def = Some(src);
+            msg.to_string()
+        }
+        ReleaseChannel => {
+            let release_channel = option_env!("CFG_RELEASE_CHANNEL").unwrap_or("(unknown)");
+            note = Some(format!("this feature may not be used in the {} release channel",
+                                release_channel));
             msg.to_string()
         }
     };
@@ -410,7 +442,11 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint,
         _ => sess.bug("impossible level in raw_emit_lint"),
     }
 
-    for span in note.into_iter() {
+    for note in note.into_iter() {
+        sess.note(note.index(&FullRange));
+    }
+
+    for span in def.into_iter() {
         sess.span_note(span, "lint level defined here");
     }
 }
@@ -767,6 +803,10 @@ impl LintPass for GatherNodeLevels {
 /// Consumes the `lint_store` field of the `Session`.
 pub fn check_crate(tcx: &ty::ctxt,
                    exported_items: &ExportedItems) {
+
+    // If this is a feature-staged build of rustc then flip several lints to 'forbid'
+    tcx.sess.lint_store.borrow_mut().maybe_stage_features(&tcx.sess);
+
     let krate = tcx.map.krate();
     let mut cx = Context::new(tcx, krate, exported_items);
 
diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs
index 7043ea89c7d..8a266a2530b 100644
--- a/src/librustc/lint/mod.rs
+++ b/src/librustc/lint/mod.rs
@@ -248,6 +248,9 @@ pub enum LintSource {
 
     /// Lint level was set by a command-line flag.
     CommandLine,
+
+    /// Lint level was set by the release channel.
+    ReleaseChannel
 }
 
 pub type LevelSource = (Level, LintSource);
diff --git a/src/librustc/metadata/csearch.rs b/src/librustc/metadata/csearch.rs
index 72ce61b133a..39c7f80703d 100644
--- a/src/librustc/metadata/csearch.rs
+++ b/src/librustc/metadata/csearch.rs
@@ -27,6 +27,7 @@ use std::rc::Rc;
 use syntax::ast;
 use syntax::ast_map;
 use syntax::attr;
+use syntax::attr::AttrMetaMethods;
 use syntax::diagnostic::expect;
 use syntax::parse::token;
 
@@ -375,6 +376,18 @@ pub fn get_stability(cstore: &cstore::CStore,
     decoder::get_stability(&*cdata, def.node)
 }
 
+pub fn is_staged_api(cstore: &cstore::CStore, def: ast::DefId) -> bool {
+    let cdata = cstore.get_crate_data(def.krate);
+    let attrs = decoder::get_crate_attributes(cdata.data());
+    for attr in attrs.iter() {
+        if attr.name().get() == "staged_api" {
+            match attr.node.value.node { ast::MetaWord(_) => return true, _ => (/*pass*/) }
+        }
+    }
+
+    return false;
+}
+
 pub fn get_repr_attrs(cstore: &cstore::CStore, def: ast::DefId)
                       -> Vec<attr::ReprAttr> {
     let cdata = cstore.get_crate_data(def.krate);
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 359ad8d3941..e712f510d9d 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -189,3 +189,19 @@ pub fn lookup(tcx: &ty::ctxt, id: DefId) -> Option<Stability> {
         }
     })
 }
+
+pub fn is_staged_api(tcx: &ty::ctxt, id: DefId) -> bool {
+    match ty::trait_item_of_item(tcx, id) {
+        Some(ty::MethodTraitItemId(trait_method_id))
+            if trait_method_id != id => {
+                is_staged_api(tcx, trait_method_id)
+            }
+        _ if is_local(id) => {
+            // Unused case
+            unreachable!()
+        }
+        _ => {
+            csearch::is_staged_api(&tcx.sess.cstore, id)
+        }
+    }
+}
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 4968066f7b6..ff0bc69c5bf 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -111,7 +111,24 @@ pub struct Options {
     /// An optional name to use as the crate for std during std injection,
     /// written `extern crate std = "name"`. Default to "std". Used by
     /// out-of-tree drivers.
-    pub alt_std_name: Option<String>
+    pub alt_std_name: Option<String>,
+    /// Indicates how the compiler should treat unstable features
+    pub unstable_features: UnstableFeatures
+}
+
+#[deriving(Clone, Copy)]
+pub enum UnstableFeatures {
+    /// Hard errors for unstable features are active, as on
+    /// beta/stable channels.
+    Disallow,
+    /// Use the default lint levels
+    Default,
+    /// 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 this is always required for building Rust
+    /// itself.
+    Cheat
 }
 
 #[derive(Clone, PartialEq, Eq)]
@@ -217,6 +234,7 @@ pub fn basic_options() -> Options {
         crate_name: None,
         alt_std_name: None,
         libs: Vec::new(),
+        unstable_features: UnstableFeatures::Disallow
     }
 }
 
@@ -1149,6 +1167,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
         crate_name: crate_name,
         alt_std_name: None,
         libs: libs,
+        unstable_features: UnstableFeatures::Disallow
     }
 }
 
diff --git a/src/librustc_back/lib.rs b/src/librustc_back/lib.rs
index ca39477fbdc..5d863def32e 100644
--- a/src/librustc_back/lib.rs
+++ b/src/librustc_back/lib.rs
@@ -23,6 +23,7 @@
 
 #![crate_name = "rustc_back"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc_borrowck/lib.rs b/src/librustc_borrowck/lib.rs
index 26bcd5f4c10..452eaaaa52d 100644
--- a/src/librustc_borrowck/lib.rs
+++ b/src/librustc_borrowck/lib.rs
@@ -10,6 +10,7 @@
 
 #![crate_name = "rustc_borrowck"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs
index 5af114abeea..1c2d956e279 100644
--- a/src/librustc_driver/lib.rs
+++ b/src/librustc_driver/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "rustc_driver"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
@@ -46,7 +47,7 @@ pub use syntax::diagnostic;
 
 use rustc_trans::back::link;
 use rustc::session::{config, Session, build_session};
-use rustc::session::config::{Input, PrintRequest};
+use rustc::session::config::{Input, PrintRequest, UnstableFeatures};
 use rustc::lint::Lint;
 use rustc::lint;
 use rustc::metadata;
@@ -132,7 +133,11 @@ fn run_compiler(args: &[String]) {
         _ => early_error("multiple input filenames provided")
     };
 
+    let mut sopts = sopts;
+    sopts.unstable_features = get_unstable_features_setting();
+
     let mut sess = build_session(sopts, input_file_path, descriptions);
+
     let cfg = config::build_configuration(&sess);
     if print_crate_info(&sess, Some(&input), &odir, &ofile) {
         return
@@ -181,6 +186,21 @@ fn run_compiler(args: &[String]) {
     driver::compile_input(sess, cfg, &input, &odir, &ofile, None);
 }
 
+pub fn get_unstable_features_setting() -> UnstableFeatures {
+    // Whether 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();
+    // The secret key needed to get through the rustc build itself by
+    // subverting the unstable features lints
+    let bootstrap_secret_key = option_env!("CFG_BOOTSTRAP_KEY");
+    // The matching key to the above, only known by the build system
+    let bootstrap_provided_key = os::getenv("RUSTC_BOOTSTRAP_KEY");
+    match (disable_unstable_features, bootstrap_secret_key, bootstrap_provided_key) {
+        (_, Some(ref s), Some(ref p)) if s == p => UnstableFeatures::Cheat,
+        (true, _, _) => UnstableFeatures::Disallow,
+        (false, _, _) => UnstableFeatures::Default
+    }
+}
+
 /// Returns a version string such as "0.12.0-dev".
 pub fn release_str() -> Option<&'static str> {
     option_env!("CFG_RELEASE")
diff --git a/src/librustc_llvm/lib.rs b/src/librustc_llvm/lib.rs
index 0bed754aa3c..961d3a6cfa1 100644
--- a/src/librustc_llvm/lib.rs
+++ b/src/librustc_llvm/lib.rs
@@ -15,6 +15,7 @@
 
 #![crate_name = "rustc_llvm"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 93ad69e03b1..845db8aa5df 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -10,6 +10,7 @@
 
 #![crate_name = "rustc_resolve"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs
index b6f90a4c2f5..3497ff2221c 100644
--- a/src/librustc_trans/lib.rs
+++ b/src/librustc_trans/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "rustc_trans"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs
index ae8731dfa47..85221c2e913 100644
--- a/src/librustc_typeck/lib.rs
+++ b/src/librustc_typeck/lib.rs
@@ -65,6 +65,7 @@ This API is completely unstable and subject to change.
 
 #![crate_name = "rustc_typeck"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 46c212a9f2d..4885bd373eb 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -11,6 +11,7 @@ pub use self::MaybeTyped::*;
 
 use rustc_driver::driver;
 use rustc::session::{self, config};
+use rustc::session::config::UnstableFeatures;
 use rustc::session::search_paths::SearchPaths;
 use rustc::middle::{privacy, ty};
 use rustc::lint;
@@ -95,10 +96,11 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
         externs: externs,
         target_triple: triple.unwrap_or(config::host_triple().to_string()),
         cfg: config::parse_cfgspecs(cfgs),
+        // Ensure that rustdoc works even if rustc is feature-staged
+        unstable_features: UnstableFeatures::Default,
         ..config::basic_options().clone()
     };
 
-
     let codemap = codemap::CodeMap::new();
     let diagnostic_handler = diagnostic::default_handler(diagnostic::Auto, None);
     let span_diagnostic_handler =
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index ee65ef06623..daa930fddcb 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -10,6 +10,7 @@
 
 #![crate_name = "rustdoc"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index bbe35eb0e9c..8e0f4b2d443 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -22,6 +22,7 @@ use std::collections::{HashSet, HashMap};
 use testing;
 use rustc::session::{self, config};
 use rustc::session::search_paths::{SearchPaths, PathKind};
+use rustc_driver::get_unstable_features_setting;
 use rustc_driver::driver;
 use syntax::ast;
 use syntax::codemap::{CodeMap, dummy_spanned};
@@ -52,6 +53,7 @@ pub fn run(input: &str,
         search_paths: libs.clone(),
         crate_types: vec!(config::CrateTypeDylib),
         externs: externs.clone(),
+        unstable_features: get_unstable_features_setting(),
         ..config::basic_options().clone()
     };
 
@@ -128,6 +130,7 @@ fn runtest(test: &str, cratename: &str, libs: SearchPaths,
             .. config::basic_codegen_options()
         },
         test: as_test_harness,
+        unstable_features: get_unstable_features_setting(),
         ..config::basic_options().clone()
     };
 
diff --git a/src/libserialize/lib.rs b/src/libserialize/lib.rs
index fd155261934..d1e7f6967bf 100644
--- a/src/libserialize/lib.rs
+++ b/src/libserialize/lib.rs
@@ -16,6 +16,7 @@ Core encoding and decoding interfaces.
 
 #![crate_name = "serialize"]
 #![unstable = "deprecated in favor of rustc-serialize on crates.io"]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index 6950a6cf8d0..15f279f1b45 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -96,6 +96,7 @@
 
 #![crate_name = "std"]
 #![stable]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs
index 31fe23847d9..e4460ca865b 100644
--- a/src/libsyntax/codemap.rs
+++ b/src/libsyntax/codemap.rs
@@ -590,7 +590,12 @@ impl CodeMap {
                 Some(ref info) => {
                     // save the parent expn_id for next loop iteration
                     expnid = info.call_site.expn_id;
-                    if info.callee.span.is_none() {
+                    if info.callee.name == "format_args" {
+                        // This is a hack because the format_args builtin calls unstable APIs.
+                        // I spent like 6 hours trying to solve this more generally but am stupid.
+                        is_internal = true;
+                        false
+                    } else if info.callee.span.is_none() {
                         // it's a compiler built-in, we *really* don't want to mess with it
                         // so we skip it, unless it was called by a regular macro, in which case
                         // we will handle the caller macro next turn
diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs
index 9e14f9dd1ea..7c8ccf3a1a1 100644
--- a/src/libsyntax/lib.rs
+++ b/src/libsyntax/lib.rs
@@ -16,6 +16,7 @@
 
 #![crate_name = "syntax"]
 #![experimental]
+#![staged_api]
 #![crate_type = "dylib"]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libsyntax/std_inject.rs b/src/libsyntax/std_inject.rs
index daa51203287..8e1a5860259 100644
--- a/src/libsyntax/std_inject.rs
+++ b/src/libsyntax/std_inject.rs
@@ -48,7 +48,7 @@ fn no_prelude(attrs: &[ast::Attribute]) -> bool {
 }
 
 struct StandardLibraryInjector<'a> {
-    alt_std_name: Option<String>,
+    alt_std_name: Option<String>
 }
 
 impl<'a> fold::Folder for StandardLibraryInjector<'a> {
@@ -84,14 +84,13 @@ impl<'a> fold::Folder for StandardLibraryInjector<'a> {
 
 fn inject_crates_ref(krate: ast::Crate, alt_std_name: Option<String>) -> ast::Crate {
     let mut fold = StandardLibraryInjector {
-        alt_std_name: alt_std_name,
+        alt_std_name: alt_std_name
     };
     fold.fold_crate(krate)
 }
 
 struct PreludeInjector<'a>;
 
-
 impl<'a> fold::Folder for PreludeInjector<'a> {
     fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
         // Add #![no_std] here, so we don't re-inject when compiling pretty-printed source.
@@ -104,20 +103,10 @@ impl<'a> fold::Folder for PreludeInjector<'a> {
         attr::mark_used(&no_std_attr);
         krate.attrs.push(no_std_attr);
 
+        // only add `use std::prelude::*;` if there wasn't a
+        // `#![no_implicit_prelude]` at the crate level.
+        // fold_mod() will insert glob path.
         if !no_prelude(krate.attrs.index(&FullRange)) {
-            // only add `use std::prelude::*;` if there wasn't a
-            // `#![no_implicit_prelude]` at the crate level.
-            // fold_mod() will insert glob path.
-            let globs_attr = attr::mk_attr_inner(attr::mk_attr_id(),
-                                                 attr::mk_list_item(
-                InternedString::new("feature"),
-                vec!(
-                    attr::mk_word_item(InternedString::new("globs")),
-                )));
-            // std_inject runs after feature checking so manually mark this attr
-            attr::mark_used(&globs_attr);
-            krate.attrs.push(globs_attr);
-
             krate.module = self.fold_mod(krate.module);
         }
         krate
diff --git a/src/libterm/lib.rs b/src/libterm/lib.rs
index c953f591d80..44ae00d997c 100644
--- a/src/libterm/lib.rs
+++ b/src/libterm/lib.rs
@@ -40,6 +40,7 @@
 
 #![crate_name = "term"]
 #![experimental = "use the crates.io `term` library instead"]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs
index 68d06cc4dab..d21a73de6df 100644
--- a/src/libtest/lib.rs
+++ b/src/libtest/lib.rs
@@ -25,6 +25,7 @@
 
 #![crate_name = "test"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![crate_type = "dylib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
diff --git a/src/libunicode/lib.rs b/src/libunicode/lib.rs
index db98b429e40..33b5bc4b5a4 100644
--- a/src/libunicode/lib.rs
+++ b/src/libunicode/lib.rs
@@ -22,6 +22,7 @@
 
 #![crate_name = "unicode"]
 #![experimental]
+#![staged_api]
 #![crate_type = "rlib"]
 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
diff --git a/src/test/compile-fail/feature-gate-feature-gate.rs b/src/test/compile-fail/feature-gate-feature-gate.rs
new file mode 100644
index 00000000000..b903b29658b
--- /dev/null
+++ b/src/test/compile-fail/feature-gate-feature-gate.rs
@@ -0,0 +1,14 @@
+// Copyright 2014 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.
+
+#![forbid(unstable_features)]
+#![feature(intrinsics)] //~ ERROR unstable feature
+
+fn main() { }
diff --git a/src/test/compile-fail/feature-gated-feature-in-macro-arg.rs b/src/test/compile-fail/feature-gated-feature-in-macro-arg.rs
index cd49c7c016e..1e15e67876e 100644
--- a/src/test/compile-fail/feature-gated-feature-in-macro-arg.rs
+++ b/src/test/compile-fail/feature-gated-feature-in-macro-arg.rs
@@ -8,6 +8,14 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+// FIXME #20661: format_args! emits calls to the unstable std::fmt::rt
+// module, so the compiler has some hacks to make that possible
+// (in span_is_internal). Unnfortunately those hacks defeat this
+// particular scenario of checking feature gates in arguments to
+// println!().
+
+// ignore-test
+
 // tests that input to a macro is checked for use of gated features. If this
 // test succeeds due to the acceptance of a feature, pick a new feature to
 // test. Not ideal, but oh well :(
diff --git a/src/test/pretty/issue-4264.pp b/src/test/pretty/issue-4264.pp
index f20087ef677..dac6e628d10 100644
--- a/src/test/pretty/issue-4264.pp
+++ b/src/test/pretty/issue-4264.pp
@@ -1,5 +1,4 @@
 #![no_std]
-#![feature(globs)]
 #[macro_use]
 extern crate "std" as std;
 #[prelude_import]