Auto merge of #50080 - klnusbaum:edition_49591, r=Manishearth

add --edition option

This adds an official `edition` flag to the rust compiler
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 2e6689e..e9fe94c 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -16,6 +16,8 @@
 pub use self::Passes::*;
 pub use self::DebugInfoLevel::*;
 
+use std::str::FromStr;
+
 use session::{early_error, early_warn, Session};
 use session::search_paths::SearchPaths;
 
@@ -28,7 +30,7 @@
 
 use syntax::ast::{self, IntTy, UintTy};
 use syntax::codemap::{FileName, FilePathMapping};
-use syntax::edition::Edition;
+use syntax::edition::{Edition, EDITION_NAME_LIST, DEFAULT_EDITION};
 use syntax::parse::token;
 use syntax::parse;
 use syntax::symbol::Symbol;
@@ -410,6 +412,7 @@
 
         // Remap source path prefixes in all output (messages, object files, debug, etc)
         remap_path_prefix: Vec<(PathBuf, PathBuf)> [UNTRACKED],
+        edition: Edition [TRACKED],
     }
 );
 
@@ -589,6 +592,7 @@
         cli_forced_codegen_units: None,
         cli_forced_thinlto_off: false,
         remap_path_prefix: Vec::new(),
+        edition: DEFAULT_EDITION,
     }
 }
 
@@ -773,8 +777,6 @@
             Some("`string` or `string=string`");
         pub const parse_lto: Option<&'static str> =
             Some("one of `thin`, `fat`, or omitted");
-        pub const parse_edition: Option<&'static str> =
-            Some("one of: `2015`, `2018`");
     }
 
     #[allow(dead_code)]
@@ -782,7 +784,6 @@
         use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitizer, Lto};
         use rustc_back::{LinkerFlavor, PanicStrategy, RelroLevel};
         use std::path::PathBuf;
-        use syntax::edition::Edition;
 
         $(
             pub fn $opt(cg: &mut $struct_name, v: Option<&str>) -> bool {
@@ -985,20 +986,6 @@
             true
         }
 
-        fn parse_edition(slot: &mut Edition, v: Option<&str>) -> bool {
-            match v {
-                Some(s) => {
-                    let edition = s.parse();
-                    if let Ok(parsed) = edition {
-                        *slot = parsed;
-                        true
-                    } else {
-                        false
-                    }
-                }
-                _ => false,
-            }
-        }
     }
 ) }
 
@@ -1292,10 +1279,6 @@
         `everybody_loops` (all function bodies replaced with `loop {}`),
         `hir` (the HIR), `hir,identified`, or
         `hir,typed` (HIR with types for each node)."),
-    edition: Edition = (Edition::Edition2015, parse_edition, [TRACKED],
-        "The edition to build Rust with. Newer editions may include features
-         that require breaking changes. The default edition is 2015 (the first
-         edition). Crates compiled with different editions can be linked together."),
     run_dsymutil: Option<bool> = (None, parse_opt_bool, [TRACKED],
           "run `dsymutil` and delete intermediate object files"),
     ui_testing: bool = (false, parse_bool, [UNTRACKED],
@@ -1656,6 +1639,12 @@
                   `expanded,identified` (fully parenthesized, AST nodes with IDs).",
             "TYPE",
         ),
+        opt::opt_s(
+            "",
+            "edition",
+            "Specify which edition of the compiler to use when compiling code.",
+            EDITION_NAME_LIST,
+        ),
         opt::multi_s(
             "",
             "remap-path-prefix",
@@ -1715,6 +1704,34 @@
         ),
     };
 
+    let edition = match matches.opt_str("edition") {
+        Some(arg) => match Edition::from_str(&arg){
+            Ok(edition) => edition,
+            Err(_) => early_error(
+                ErrorOutputType::default(),
+                &format!(
+                    "argument for --edition must be one of: \
+                    {}. (instead was `{}`)",
+                    EDITION_NAME_LIST,
+                    arg
+                ),
+            ),
+        }
+        None => DEFAULT_EDITION,
+    };
+
+    if !edition.is_stable() && !nightly_options::is_nightly_build() {
+        early_error(
+                ErrorOutputType::default(),
+                &format!(
+                    "Edition {} is unstable an only\
+                    available for nightly builds of rustc.",
+                    edition,
+                )
+        )
+    }
+
+
     // We need the opts_present check because the driver will send us Matches
     // with only stable options if no unstable options are used. Since error-format
     // is unstable, it will not be present. We have to use opts_present not
@@ -2171,6 +2188,7 @@
             cli_forced_codegen_units: codegen_units,
             cli_forced_thinlto_off: disable_thinlto,
             remap_path_prefix,
+            edition,
         },
         cfg,
     )
@@ -2300,11 +2318,12 @@
     use std::hash::Hash;
     use std::path::PathBuf;
     use std::collections::hash_map::DefaultHasher;
-    use super::{CrateType, DebugInfoLevel, Edition, ErrorOutputType, Lto, OptLevel, OutputTypes,
+    use super::{CrateType, DebugInfoLevel, ErrorOutputType, Lto, OptLevel, OutputTypes,
                 Passes, Sanitizer};
     use syntax::feature_gate::UnstableFeatures;
     use rustc_back::{PanicStrategy, RelroLevel};
     use rustc_back::target::TargetTriple;
+    use syntax::edition::Edition;
 
     pub trait DepTrackingHash {
         fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType);
@@ -2363,8 +2382,8 @@
     impl_dep_tracking_hash_via_hash!(cstore::NativeLibraryKind);
     impl_dep_tracking_hash_via_hash!(Sanitizer);
     impl_dep_tracking_hash_via_hash!(Option<Sanitizer>);
-    impl_dep_tracking_hash_via_hash!(Edition);
     impl_dep_tracking_hash_via_hash!(TargetTriple);
+    impl_dep_tracking_hash_via_hash!(Edition);
 
     impl_dep_tracking_hash_for_sortable_vec_of!(String);
     impl_dep_tracking_hash_for_sortable_vec_of!(PathBuf);
@@ -2437,6 +2456,7 @@
     use super::{Externs, OutputType, OutputTypes};
     use rustc_back::{PanicStrategy, RelroLevel};
     use syntax::symbol::Symbol;
+    use syntax::edition::{Edition, DEFAULT_EDITION};
     use syntax;
 
     fn optgroups() -> getopts::Options {
@@ -3081,4 +3101,17 @@
         opts.debugging_opts.relro_level = Some(RelroLevel::Full);
         assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
     }
+
+    #[test]
+    fn test_edition_parsing() {
+        // test default edition
+        let options = super::basic_options();
+        assert!(options.edition == DEFAULT_EDITION);
+
+        let matches = optgroups()
+            .parse(&["--edition=2018".to_string()])
+            .unwrap();
+        let (sessopts, _) = build_session_options_and_crate_config(&matches);
+        assert!(sessopts.edition == Edition::Edition2018)
+    }
 }
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 2993234..5e4dee7 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -934,11 +934,11 @@
 
     /// Are we allowed to use features from the Rust 2018 edition?
     pub fn rust_2018(&self) -> bool {
-        self.opts.debugging_opts.edition >= Edition::Edition2018
+        self.opts.edition >= Edition::Edition2018
     }
 
     pub fn edition(&self) -> Edition {
-        self.opts.debugging_opts.edition
+        self.opts.edition
     }
 }
 
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index f8c0289..2c781fd 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -691,7 +691,7 @@
         krate,
         &sess.parse_sess,
         sess.opts.test,
-        sess.opts.debugging_opts.edition,
+        sess.edition(),
     );
     // these need to be set "early" so that expansion sees `quote` if enabled.
     sess.init_features(features);
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 9fb024f..2bd1e72 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -155,10 +155,10 @@
         actually_rustdoc: true,
         debugging_opts: config::DebuggingOptions {
             force_unstable_if_unmarked,
-            edition,
             ..config::basic_debugging_options()
         },
         error_format,
+        edition,
         ..config::basic_options().clone()
     };
 
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 8cd5f37..c4eaa48 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -80,9 +80,9 @@
         lint_cap: Some(::rustc::lint::Level::Allow),
         actually_rustdoc: true,
         debugging_opts: config::DebuggingOptions {
-            edition,
             ..config::basic_debugging_options()
         },
+        edition,
         ..config::basic_options().clone()
     };
 
@@ -223,9 +223,9 @@
         test: as_test_harness,
         unstable_features: UnstableFeatures::from_environment(),
         debugging_opts: config::DebuggingOptions {
-            edition,
             ..config::basic_debugging_options()
         },
+        edition,
         ..config::basic_options().clone()
     };
 
diff --git a/src/libsyntax/edition.rs b/src/libsyntax/edition.rs
index e579fc7..3fc1c27 100644
--- a/src/libsyntax/edition.rs
+++ b/src/libsyntax/edition.rs
@@ -24,20 +24,19 @@
 
     // when adding new editions, be sure to update:
     //
-    // - the list in the `parse_edition` static in librustc::session::config
+    // - Update the `ALL_EDITIONS` const
+    // - Update the EDITION_NAME_LIST const
     // - add a `rust_####()` function to the session
     // - update the enum in Cargo's sources as well
-    //
-    // When -Zedition becomes --edition, there will
-    // also be a check for the edition being nightly-only
-    // somewhere. That will need to be updated
-    // whenever we're stabilizing/introducing a new edition
-    // as well as changing the default Cargo template.
 }
 
 // must be in order from oldest to newest
 pub const ALL_EDITIONS: &[Edition] = &[Edition::Edition2015, Edition::Edition2018];
 
+pub const EDITION_NAME_LIST: &'static str = "2015|2018";
+
+pub const DEFAULT_EDITION: Edition = Edition::Edition2015;
+
 impl fmt::Display for Edition {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let s = match *self {
@@ -62,6 +61,13 @@
             Edition::Edition2018 => "rust_2018_preview",
         }
     }
+
+    pub fn is_stable(&self) -> bool {
+        match *self {
+            Edition::Edition2015 => true,
+            Edition::Edition2018 => false,
+        }
+    }
 }
 
 impl FromStr for Edition {
diff --git a/src/test/compile-fail/edition-raw-pointer-method-2015.rs b/src/test/compile-fail/edition-raw-pointer-method-2015.rs
index fdc9b4f..b304443 100644
--- a/src/test/compile-fail/edition-raw-pointer-method-2015.rs
+++ b/src/test/compile-fail/edition-raw-pointer-method-2015.rs
@@ -9,7 +9,7 @@
 // except according to those terms.
 
 // ignore-tidy-linelength
-// compile-flags: -Zedition=2015 -Zunstable-options
+// compile-flags: --edition=2015 -Zunstable-options
 
 // tests that editions work with the tyvar warning-turned-error
 
diff --git a/src/test/compile-fail/edition-raw-pointer-method-2018.rs b/src/test/compile-fail/edition-raw-pointer-method-2018.rs
index 58b3459..d01cac0 100644
--- a/src/test/compile-fail/edition-raw-pointer-method-2018.rs
+++ b/src/test/compile-fail/edition-raw-pointer-method-2018.rs
@@ -9,7 +9,7 @@
 // except according to those terms.
 
 // ignore-tidy-linelength
-// compile-flags: -Zedition=2018 -Zunstable-options
+// compile-flags: --edition=2018 -Zunstable-options
 
 // tests that editions work with the tyvar warning-turned-error