1
Fork 0

rustc: Refactor how unstable flags are handled

This commit adds support for *truly* unstable options in the compiler, as well
as adding warnings for the start of the deprecation path of
unstable-but-not-really options. Specifically, the following behavior is now in
place for handling unstable options:

* As before, an unconditional error is emitted if an unstable option is passed
  and the `-Z unstable-options` flag is not present. Note that passing another
  `-Z` flag does not require passing `-Z unstable-options` as well.
* New flags added to the compiler will be in the `Unstable` category as opposed
  to the `UnstableButNotReally` category which means they will unconditionally
  emit an error when used on stable.
* All current flags are in a category where they will emit warnings when used
  that the option will soon be a hard error.

Also as before, it is intended that `-Z` is akin to `#![feature]` in a crate
where it is required to unlock unstable functionality. A nightly compiler which
is used without any `-Z` flags should only be exercising stable behavior.
This commit is contained in:
Alex Crichton 2016-02-19 22:03:54 -08:00
parent 0ef8d42605
commit 1282833470
2 changed files with 217 additions and 118 deletions

View file

@ -749,24 +749,20 @@ pub fn build_target_config(opts: &Options, sp: &Handler) -> Config {
} }
} }
/// Returns the "short" subset of the stable rustc command line options.
pub fn short_optgroups() -> Vec<getopts::OptGroup> {
rustc_short_optgroups().into_iter()
.filter(|g|g.is_stable())
.map(|g|g.opt_group)
.collect()
}
/// Returns all of the stable rustc command line options.
pub fn optgroups() -> Vec<getopts::OptGroup> {
rustc_optgroups().into_iter()
.filter(|g|g.is_stable())
.map(|g|g.opt_group)
.collect()
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum OptionStability { Stable, Unstable } pub enum OptionStability {
Stable,
// FIXME: historically there were some options which were either `-Z` or
// required the `-Z unstable-options` flag, which were all intended
// to be unstable. Unfortunately we didn't actually gate usage of
// these options on the stable compiler, so we still allow them there
// today. There are some warnings printed out about this in the
// driver.
UnstableButNotReally,
Unstable,
}
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct RustcOptGroup { pub struct RustcOptGroup {
@ -783,9 +779,17 @@ impl RustcOptGroup {
RustcOptGroup { opt_group: g, stability: OptionStability::Stable } RustcOptGroup { opt_group: g, stability: OptionStability::Stable }
} }
#[allow(dead_code)] // currently we have no "truly unstable" options
fn unstable(g: getopts::OptGroup) -> RustcOptGroup { fn unstable(g: getopts::OptGroup) -> RustcOptGroup {
RustcOptGroup { opt_group: g, stability: OptionStability::Unstable } RustcOptGroup { opt_group: g, stability: OptionStability::Unstable }
} }
fn unstable_bnr(g: getopts::OptGroup) -> RustcOptGroup {
RustcOptGroup {
opt_group: g,
stability: OptionStability::UnstableButNotReally,
}
}
} }
// The `opt` local module holds wrappers around the `getopts` API that // The `opt` local module holds wrappers around the `getopts` API that
@ -807,24 +811,57 @@ mod opt {
fn stable(g: getopts::OptGroup) -> R { RustcOptGroup::stable(g) } fn stable(g: getopts::OptGroup) -> R { RustcOptGroup::stable(g) }
fn unstable(g: getopts::OptGroup) -> R { RustcOptGroup::unstable(g) } fn unstable(g: getopts::OptGroup) -> R { RustcOptGroup::unstable(g) }
fn unstable_bnr(g: getopts::OptGroup) -> R { RustcOptGroup::unstable_bnr(g) }
// FIXME (pnkfelix): We default to stable since the current set of pub fn opt_s(a: S, b: S, c: S, d: S) -> R {
// options is defacto stable. However, it would be good to revise the stable(getopts::optopt(a, b, c, d))
// code so that a stable option is the thing that takes extra effort }
// to encode. pub fn multi_s(a: S, b: S, c: S, d: S) -> R {
stable(getopts::optmulti(a, b, c, d))
}
pub fn flag_s(a: S, b: S, c: S) -> R {
stable(getopts::optflag(a, b, c))
}
pub fn flagopt_s(a: S, b: S, c: S, d: S) -> R {
stable(getopts::optflagopt(a, b, c, d))
}
pub fn flagmulti_s(a: S, b: S, c: S) -> R {
stable(getopts::optflagmulti(a, b, c))
}
pub fn opt(a: S, b: S, c: S, d: S) -> R { stable(getopts::optopt(a, b, c, d)) } pub fn opt(a: S, b: S, c: S, d: S) -> R {
pub fn multi(a: S, b: S, c: S, d: S) -> R { stable(getopts::optmulti(a, b, c, d)) } unstable(getopts::optopt(a, b, c, d))
pub fn flag(a: S, b: S, c: S) -> R { stable(getopts::optflag(a, b, c)) } }
pub fn flagopt(a: S, b: S, c: S, d: S) -> R { stable(getopts::optflagopt(a, b, c, d)) } pub fn multi(a: S, b: S, c: S, d: S) -> R {
pub fn flagmulti(a: S, b: S, c: S) -> R { stable(getopts::optflagmulti(a, b, c)) } unstable(getopts::optmulti(a, b, c, d))
}
pub fn flag(a: S, b: S, c: S) -> R {
unstable(getopts::optflag(a, b, c))
}
pub fn flagopt(a: S, b: S, c: S, d: S) -> R {
unstable(getopts::optflagopt(a, b, c, d))
}
pub fn flagmulti(a: S, b: S, c: S) -> R {
unstable(getopts::optflagmulti(a, b, c))
}
// Do not use these functions for any new options added to the compiler, all
pub fn opt_u(a: S, b: S, c: S, d: S) -> R { unstable(getopts::optopt(a, b, c, d)) } // new options should use the `*_u` variants above to be truly unstable.
pub fn multi_u(a: S, b: S, c: S, d: S) -> R { unstable(getopts::optmulti(a, b, c, d)) } pub fn opt_ubnr(a: S, b: S, c: S, d: S) -> R {
pub fn flag_u(a: S, b: S, c: S) -> R { unstable(getopts::optflag(a, b, c)) } unstable_bnr(getopts::optopt(a, b, c, d))
pub fn flagopt_u(a: S, b: S, c: S, d: S) -> R { unstable(getopts::optflagopt(a, b, c, d)) } }
pub fn flagmulti_u(a: S, b: S, c: S) -> R { unstable(getopts::optflagmulti(a, b, c)) } pub fn multi_ubnr(a: S, b: S, c: S, d: S) -> R {
unstable_bnr(getopts::optmulti(a, b, c, d))
}
pub fn flag_ubnr(a: S, b: S, c: S) -> R {
unstable_bnr(getopts::optflag(a, b, c))
}
pub fn flagopt_ubnr(a: S, b: S, c: S, d: S) -> R {
unstable_bnr(getopts::optflagopt(a, b, c, d))
}
pub fn flagmulti_ubnr(a: S, b: S, c: S) -> R {
unstable_bnr(getopts::optflagmulti(a, b, c))
}
} }
/// Returns the "short" subset of the rustc command line options, /// Returns the "short" subset of the rustc command line options,
@ -832,44 +869,44 @@ mod opt {
/// part of the stable long-term interface for rustc. /// part of the stable long-term interface for rustc.
pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> { pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
vec![ vec![
opt::flag("h", "help", "Display this message"), opt::flag_s("h", "help", "Display this message"),
opt::multi("", "cfg", "Configure the compilation environment", "SPEC"), opt::multi_s("", "cfg", "Configure the compilation environment", "SPEC"),
opt::multi("L", "", "Add a directory to the library search path", opt::multi_s("L", "", "Add a directory to the library search path",
"[KIND=]PATH"), "[KIND=]PATH"),
opt::multi("l", "", "Link the generated crate(s) to the specified native opt::multi_s("l", "", "Link the generated crate(s) to the specified native
library NAME. The optional KIND can be one of, library NAME. The optional KIND can be one of,
static, dylib, or framework. If omitted, dylib is static, dylib, or framework. If omitted, dylib is
assumed.", "[KIND=]NAME"), assumed.", "[KIND=]NAME"),
opt::multi("", "crate-type", "Comma separated list of types of crates opt::multi_s("", "crate-type", "Comma separated list of types of crates
for the compiler to emit", for the compiler to emit",
"[bin|lib|rlib|dylib|staticlib]"), "[bin|lib|rlib|dylib|staticlib]"),
opt::opt("", "crate-name", "Specify the name of the crate being built", opt::opt_s("", "crate-name", "Specify the name of the crate being built",
"NAME"), "NAME"),
opt::multi("", "emit", "Comma separated list of types of output for \ opt::multi_s("", "emit", "Comma separated list of types of output for \
the compiler to emit", the compiler to emit",
"[asm|llvm-bc|llvm-ir|obj|link|dep-info]"), "[asm|llvm-bc|llvm-ir|obj|link|dep-info]"),
opt::multi("", "print", "Comma separated list of compiler information to \ opt::multi_s("", "print", "Comma separated list of compiler information to \
print on stdout", print on stdout",
"[crate-name|file-names|sysroot|target-list]"), "[crate-name|file-names|sysroot|target-list]"),
opt::flagmulti("g", "", "Equivalent to -C debuginfo=2"), opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"),
opt::flagmulti("O", "", "Equivalent to -C opt-level=2"), opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"),
opt::opt("o", "", "Write output to <filename>", "FILENAME"), opt::opt_s("o", "", "Write output to <filename>", "FILENAME"),
opt::opt("", "out-dir", "Write output to compiler-chosen filename \ opt::opt_s("", "out-dir", "Write output to compiler-chosen filename \
in <dir>", "DIR"), in <dir>", "DIR"),
opt::opt("", "explain", "Provide a detailed explanation of an error \ opt::opt_s("", "explain", "Provide a detailed explanation of an error \
message", "OPT"), message", "OPT"),
opt::flag("", "test", "Build a test harness"), opt::flag_s("", "test", "Build a test harness"),
opt::opt("", "target", "Target triple for which the code is compiled", "TARGET"), opt::opt_s("", "target", "Target triple for which the code is compiled", "TARGET"),
opt::multi("W", "warn", "Set lint warnings", "OPT"), opt::multi_s("W", "warn", "Set lint warnings", "OPT"),
opt::multi("A", "allow", "Set lint allowed", "OPT"), opt::multi_s("A", "allow", "Set lint allowed", "OPT"),
opt::multi("D", "deny", "Set lint denied", "OPT"), opt::multi_s("D", "deny", "Set lint denied", "OPT"),
opt::multi("F", "forbid", "Set lint forbidden", "OPT"), opt::multi_s("F", "forbid", "Set lint forbidden", "OPT"),
opt::multi("", "cap-lints", "Set the most restrictive lint level. \ opt::multi_s("", "cap-lints", "Set the most restrictive lint level. \
More restrictive lints are capped at this \ More restrictive lints are capped at this \
level", "LEVEL"), level", "LEVEL"),
opt::multi("C", "codegen", "Set a codegen option", "OPT[=VALUE]"), opt::multi_s("C", "codegen", "Set a codegen option", "OPT[=VALUE]"),
opt::flag("V", "version", "Print version info and exit"), opt::flag_s("V", "version", "Print version info and exit"),
opt::flag("v", "verbose", "Use verbose output"), opt::flag_s("v", "verbose", "Use verbose output"),
] ]
} }
@ -879,24 +916,26 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
pub fn rustc_optgroups() -> Vec<RustcOptGroup> { pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
let mut opts = rustc_short_optgroups(); let mut opts = rustc_short_optgroups();
opts.extend_from_slice(&[ opts.extend_from_slice(&[
opt::multi("", "extern", "Specify where an external rust library is \ opt::multi_s("", "extern", "Specify where an external rust library is \
located", located",
"NAME=PATH"), "NAME=PATH"),
opt::opt("", "sysroot", "Override the system root", "PATH"), opt::opt_s("", "sysroot", "Override the system root", "PATH"),
opt::multi("Z", "", "Set internal debugging options", "FLAG"), opt::multi_ubnr("Z", "", "Set internal debugging options", "FLAG"),
opt::opt_u("", "error-format", "How errors and other messages are produced", "human|json"), opt::opt_ubnr("", "error-format",
opt::opt("", "color", "Configure coloring of output: "How errors and other messages are produced",
"human|json"),
opt::opt_s("", "color", "Configure coloring of output:
auto = colorize, if output goes to a tty (default); auto = colorize, if output goes to a tty (default);
always = always colorize output; always = always colorize output;
never = never colorize output", "auto|always|never"), never = never colorize output", "auto|always|never"),
opt::flagopt_u("", "pretty", opt::flagopt_ubnr("", "pretty",
"Pretty-print the input instead of compiling; "Pretty-print the input instead of compiling;
valid types are: `normal` (un-annotated source), valid types are: `normal` (un-annotated source),
`expanded` (crates expanded), or `expanded` (crates expanded), or
`expanded,identified` (fully parenthesized, AST nodes with IDs).", `expanded,identified` (fully parenthesized, AST nodes with IDs).",
"TYPE"), "TYPE"),
opt::flagopt_u("", "unpretty", opt::flagopt_ubnr("", "unpretty",
"Present the input source, unstable (and less-pretty) variants; "Present the input source, unstable (and less-pretty) variants;
valid types are any of the types for `--pretty`, as well as: valid types are any of the types for `--pretty`, as well as:
`flowgraph=<nodeid>` (graphviz formatted flowgraph for node), `flowgraph=<nodeid>` (graphviz formatted flowgraph for node),
@ -904,6 +943,14 @@ pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
`hir` (the HIR), `hir,identified`, or `hir` (the HIR), `hir,identified`, or
`hir,typed` (HIR with types for each node).", `hir,typed` (HIR with types for each node).",
"TYPE"), "TYPE"),
// new options here should **not** use the `_ubnr` functions, all new
// unstable options should use the short variants to indicate that they
// are truly unstable. All `_ubnr` flags are just that way because they
// were so historically.
//
// You may also wish to keep this comment at the bottom of this list to
// ensure that others see it.
]); ]);
opts opts
} }
@ -1242,15 +1289,21 @@ impl fmt::Display for CrateType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use middle::cstore::DummyCrateStore; use middle::cstore::DummyCrateStore;
use session::config::{build_configuration, optgroups, build_session_options}; use session::config::{build_configuration, build_session_options};
use session::build_session; use session::build_session;
use std::rc::Rc; use std::rc::Rc;
use getopts::getopts; use getopts::{getopts, OptGroup};
use syntax::attr; use syntax::attr;
use syntax::attr::AttrMetaMethods; use syntax::attr::AttrMetaMethods;
use syntax::diagnostics; use syntax::diagnostics;
fn optgroups() -> Vec<OptGroup> {
super::rustc_optgroups().into_iter()
.map(|a| a.opt_group)
.collect()
}
// When the user supplies --test we should implicitly supply --cfg test // When the user supplies --test we should implicitly supply --cfg test
#[test] #[test]
fn test_switch_implies_cfg_test() { fn test_switch_implies_cfg_test() {

View file

@ -65,6 +65,7 @@ use rustc_trans::back::link;
use rustc_trans::save; use rustc_trans::save;
use rustc::session::{config, Session, build_session, CompileResult}; use rustc::session::{config, Session, build_session, CompileResult};
use rustc::session::config::{Input, PrintRequest, OutputType, ErrorOutputType}; use rustc::session::config::{Input, PrintRequest, OutputType, ErrorOutputType};
use rustc::session::config::{get_unstable_features_setting, OptionStability};
use rustc::middle::cstore::CrateStore; use rustc::middle::cstore::CrateStore;
use rustc::lint::Lint; use rustc::lint::Lint;
use rustc::lint; use rustc::lint;
@ -85,7 +86,7 @@ use std::str;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use rustc::session::early_error; use rustc::session::{early_error, early_warn};
use syntax::ast; use syntax::ast;
use syntax::parse; use syntax::parse;
@ -93,6 +94,7 @@ use syntax::errors;
use syntax::errors::emitter::Emitter; use syntax::errors::emitter::Emitter;
use syntax::diagnostics; use syntax::diagnostics;
use syntax::parse::token; use syntax::parse::token;
use syntax::feature_gate::UnstableFeatures;
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
@ -819,8 +821,31 @@ fn print_flag_list<T>(cmdline_opt: &str,
} }
/// Process command line options. Emits messages as appropriate. If compilation /// Process command line options. Emits messages as appropriate. If compilation
/// should continue, returns a getopts::Matches object parsed from args, otherwise /// should continue, returns a getopts::Matches object parsed from args,
/// returns None. /// otherwise returns None.
///
/// The compiler's handling of options is a little complication as it ties into
/// our stability story, and it's even *more* complicated by historical
/// accidents. The current intention of each compiler option is to have one of
/// three modes:
///
/// 1. An option is stable and can be used everywhere.
/// 2. An option is unstable, but was historically allowed on the stable
/// channel.
/// 3. An option is unstable, and can only be used on nightly.
///
/// Like unstable library and language features, however, unstable options have
/// always required a form of "opt in" to indicate that you're using them. This
/// provides the easy ability to scan a code base to check to see if anything
/// unstable is being used. Currently, this "opt in" is the `-Z` "zed" flag.
///
/// All options behind `-Z` are considered unstable by default. Other top-level
/// options can also be considered unstable, and they were unlocked through the
/// `-Z unstable-options` flag. Note that `-Z` remains to be the root of
/// instability in both cases, though.
///
/// So with all that in mind, the comments below have some more detail about the
/// contortions done here to get things to work out correctly.
pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> { pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> {
// Throw away the first argument, the name of the binary // Throw away the first argument, the name of the binary
let _binary = args.remove(0); let _binary = args.remove(0);
@ -832,62 +857,83 @@ pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> {
return None; return None;
} }
fn allows_unstable_options(matches: &getopts::Matches) -> bool { // Parse with *all* options defined in the compiler, we don't worry about
let r = matches.opt_strs("Z"); // option stability here we just want to parse as much as possible.
r.iter().any(|x| *x == "unstable-options") let all_groups: Vec<getopts::OptGroup> = config::rustc_optgroups()
} .into_iter()
.map(|x| x.opt_group)
fn parse_all_options(args: &Vec<String>) -> getopts::Matches { .collect();
let all_groups: Vec<getopts::OptGroup> = config::rustc_optgroups() let matches = match getopts::getopts(&args[..], &all_groups) {
.into_iter()
.map(|x| x.opt_group)
.collect();
match getopts::getopts(&args[..], &all_groups) {
Ok(m) => {
if !allows_unstable_options(&m) {
// If -Z unstable-options was not specified, verify that
// no unstable options were present.
for opt in config::rustc_optgroups().into_iter().filter(|x| !x.is_stable()) {
let opt_name = if !opt.opt_group.long_name.is_empty() {
&opt.opt_group.long_name
} else {
&opt.opt_group.short_name
};
if m.opt_present(opt_name) {
early_error(ErrorOutputType::default(),
&format!("use of unstable option '{}' requires -Z \
unstable-options",
opt_name));
}
}
}
m
}
Err(f) => early_error(ErrorOutputType::default(), &f.to_string()),
}
}
// As a speed optimization, first try to parse the command-line using just
// the stable options.
let matches = match getopts::getopts(&args[..], &config::optgroups()) {
Ok(ref m) if allows_unstable_options(m) => {
// If -Z unstable-options was specified, redo parsing with the
// unstable options to ensure that unstable options are defined
// in the returned getopts::Matches.
parse_all_options(&args)
}
Ok(m) => m, Ok(m) => m,
Err(_) => { Err(f) => early_error(ErrorOutputType::default(), &f.to_string()),
// redo option parsing, including unstable options this time,
// in anticipation that the mishandled option was one of the
// unstable ones.
parse_all_options(&args)
}
}; };
// For all options we just parsed, we check a few aspects:
//
// * If the option is stable, we're all good
// * If the option wasn't passed, we're all good
// * If `-Z unstable-options` wasn't passed (and we're not a -Z option
// ourselves), then we require the `-Z unstable-options` flag to unlock
// this option that was passed.
// * If we're a nightly compiler, then unstable options are now unlocked, so
// we're good to go.
// * Otherwise, if we're a truly unstable option then we generate an error
// (unstable option being used on stable)
// * If we're a historically stable-but-should-be-unstable option then we
// emit a warning that we're going to turn this into an error soon.
let has_z_unstable_options = matches.opt_strs("Z")
.iter()
.any(|x| *x == "unstable-options");
let really_allows_unstable_options = match get_unstable_features_setting() {
UnstableFeatures::Disallow => false,
_ => true,
};
for opt in config::rustc_optgroups() {
if opt.stability == OptionStability::Stable {
continue
}
let opt_name = if !opt.opt_group.long_name.is_empty() {
&opt.opt_group.long_name
} else {
&opt.opt_group.short_name
};
if !matches.opt_present(opt_name) {
continue
}
if opt_name != "Z" && !has_z_unstable_options {
let msg = format!("the `-Z unstable-options` flag must also be \
passed to enable the flag `{}`", opt_name);
early_error(ErrorOutputType::default(), &msg);
}
if really_allows_unstable_options {
continue
}
match opt.stability {
OptionStability::Unstable => {
let msg = format!("the option `{}` is only accepted on the \
nightly compiler", opt_name);
early_error(ErrorOutputType::default(), &msg);
}
OptionStability::UnstableButNotReally => {
let msg = format!("the option `{}` is is unstable and should \
only be used on the nightly compiler, but \
it is currently accepted for backwards \
compatibility; this will soon change, \
see issue #31847 for more details",
opt_name);
early_warn(ErrorOutputType::default(), &msg);
}
OptionStability::Stable => {}
}
}
if matches.opt_present("h") || matches.opt_present("help") { if matches.opt_present("h") || matches.opt_present("help") {
// Only show unstable options in --help if we *really* accept unstable
// options, which catches the case where we got `-Z unstable-options` on
// the stable channel of Rust which was accidentally allowed
// historically.
usage(matches.opt_present("verbose"), usage(matches.opt_present("verbose"),
allows_unstable_options(&matches)); has_z_unstable_options && really_allows_unstable_options);
return None; return None;
} }