Skip to content

Commit

Permalink
Preliminary feature staging
Browse files Browse the repository at this point in the history
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 to 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 rust-lang#20661.

Closes rust-lang#16678

[rc]: https://github.com/rust-lang/rfcs/blob/master/text/0507-release-channels.md
  • Loading branch information
brson committed Jan 7, 2015
1 parent 9f1ead8 commit c27133e
Show file tree
Hide file tree
Showing 43 changed files with 272 additions and 34 deletions.
12 changes: 12 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions mk/main.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/liballoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libarena/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libcollections/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libflate/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libfmt_macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libgetopts/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libgraphviz/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/liblibc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/liblog/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/librand/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#![no_std]
#![experimental]
#![staged_api]

#[macro_use]
extern crate core;
Expand Down
1 change: 1 addition & 0 deletions src/librbml/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/libregex/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down
1 change: 1 addition & 0 deletions src/librustc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
77 changes: 67 additions & 10 deletions src/librustc/lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1643,19 +1643,27 @@ 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)]
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 =>
Expand All @@ -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) {
Expand Down Expand Up @@ -1746,6 +1783,7 @@ impl LintPass for Stability {
}
_ => return
};

self.lint(cx, id, span);
}

Expand Down Expand Up @@ -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");
}
}
}
46 changes: 43 additions & 3 deletions src/librustc/lint/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -210,6 +211,7 @@ impl LintStore {
UnusedAllocation,
Stability,
MissingCopyImplementations,
UnstableFeatures,
);

add_builtin_with_new!(sess,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 => {
Expand All @@ -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()
}
};
Expand All @@ -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");
}
}
Expand Down Expand Up @@ -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);

Expand Down
Loading

1 comment on commit c27133e

@brson
Copy link
Owner Author

@brson brson commented on c27133e Jan 7, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=huonw p=1

Please sign in to comment.