diff --git a/Cargo.lock b/Cargo.lock index fa5e12f68dd10..39f8632d903eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3553,6 +3553,7 @@ dependencies = [ name = "tidy" version = "0.1.0" dependencies = [ + "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/libstd/sys/redox/ext/net.rs b/src/libstd/sys/redox/ext/net.rs index 096d0681959cd..b3ef5f3064c16 100644 --- a/src/libstd/sys/redox/ext/net.rs +++ b/src/libstd/sys/redox/ext/net.rs @@ -1,4 +1,4 @@ -#![stable(feature = "unix_socket_redox", since = "1.29")] +#![stable(feature = "unix_socket_redox", since = "1.29.0")] //! Unix-specific networking functionality @@ -27,7 +27,7 @@ use crate::sys::{cvt, fd::FileDesc, syscall}; /// let addr = socket.local_addr().expect("Couldn't get local address"); /// ``` #[derive(Clone)] -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] pub struct SocketAddr(()); impl SocketAddr { @@ -55,7 +55,7 @@ impl SocketAddr { /// let addr = socket.local_addr().expect("Couldn't get local address"); /// assert_eq!(addr.as_pathname(), None); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn as_pathname(&self) -> Option<&Path> { None } @@ -83,12 +83,12 @@ impl SocketAddr { /// let addr = socket.local_addr().expect("Couldn't get local address"); /// assert_eq!(addr.is_unnamed(), true); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn is_unnamed(&self) -> bool { false } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl fmt::Debug for SocketAddr { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "SocketAddr") @@ -109,10 +109,10 @@ impl fmt::Debug for SocketAddr { /// stream.read_to_string(&mut response).unwrap(); /// println!("{}", response); /// ``` -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] pub struct UnixStream(FileDesc); -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl fmt::Debug for UnixStream { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut builder = fmt.debug_struct("UnixStream"); @@ -143,7 +143,7 @@ impl UnixStream { /// } /// }; /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn connect>(path: P) -> io::Result { if let Some(s) = path.as_ref().to_str() { cvt(syscall::open(format!("chan:{}", s), syscall::O_CLOEXEC)) @@ -174,7 +174,7 @@ impl UnixStream { /// } /// }; /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn pair() -> io::Result<(UnixStream, UnixStream)> { let server = cvt(syscall::open("chan:", syscall::O_CREAT | syscall::O_CLOEXEC)) .map(FileDesc::new)?; @@ -198,7 +198,7 @@ impl UnixStream { /// let socket = UnixStream::connect("/tmp/sock").unwrap(); /// let sock_copy = socket.try_clone().expect("Couldn't clone socket"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn try_clone(&self) -> io::Result { self.0.duplicate().map(UnixStream) } @@ -213,7 +213,7 @@ impl UnixStream { /// let socket = UnixStream::connect("/tmp/sock").unwrap(); /// let addr = socket.local_addr().expect("Couldn't get local address"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn local_addr(&self) -> io::Result { Err(Error::new(ErrorKind::Other, "UnixStream::local_addr unimplemented on redox")) } @@ -228,7 +228,7 @@ impl UnixStream { /// let socket = UnixStream::connect("/tmp/sock").unwrap(); /// let addr = socket.peer_addr().expect("Couldn't get peer address"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn peer_addr(&self) -> io::Result { Err(Error::new(ErrorKind::Other, "UnixStream::peer_addr unimplemented on redox")) } @@ -267,7 +267,7 @@ impl UnixStream { /// let err = result.unwrap_err(); /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput) /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn set_read_timeout(&self, _timeout: Option) -> io::Result<()> { Err(Error::new(ErrorKind::Other, "UnixStream::set_read_timeout unimplemented on redox")) } @@ -306,7 +306,7 @@ impl UnixStream { /// let err = result.unwrap_err(); /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput) /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn set_write_timeout(&self, _timeout: Option) -> io::Result<()> { Err(Error::new(ErrorKind::Other, "UnixStream::set_write_timeout unimplemented on redox")) } @@ -323,7 +323,7 @@ impl UnixStream { /// socket.set_read_timeout(Some(Duration::new(1, 0))).expect("Couldn't set read timeout"); /// assert_eq!(socket.read_timeout().unwrap(), Some(Duration::new(1, 0))); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn read_timeout(&self) -> io::Result> { Err(Error::new(ErrorKind::Other, "UnixStream::read_timeout unimplemented on redox")) } @@ -340,7 +340,7 @@ impl UnixStream { /// socket.set_write_timeout(Some(Duration::new(1, 0))).expect("Couldn't set write timeout"); /// assert_eq!(socket.write_timeout().unwrap(), Some(Duration::new(1, 0))); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn write_timeout(&self) -> io::Result> { Err(Error::new(ErrorKind::Other, "UnixStream::write_timeout unimplemented on redox")) } @@ -355,7 +355,7 @@ impl UnixStream { /// let socket = UnixStream::connect("/tmp/sock").unwrap(); /// socket.set_nonblocking(true).expect("Couldn't set nonblocking"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { self.0.set_nonblocking(nonblocking) } @@ -375,7 +375,7 @@ impl UnixStream { /// /// # Platform specific /// On Redox this always returns `None`. - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn take_error(&self) -> io::Result> { Ok(None) } @@ -397,13 +397,13 @@ impl UnixStream { /// let socket = UnixStream::connect("/tmp/sock").unwrap(); /// socket.shutdown(Shutdown::Both).expect("shutdown function failed"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn shutdown(&self, _how: Shutdown) -> io::Result<()> { Err(Error::new(ErrorKind::Other, "UnixStream::shutdown unimplemented on redox")) } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl io::Read for UnixStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { io::Read::read(&mut &*self, buf) @@ -415,7 +415,7 @@ impl io::Read for UnixStream { } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl<'a> io::Read for &'a UnixStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) @@ -427,7 +427,7 @@ impl<'a> io::Read for &'a UnixStream { } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl io::Write for UnixStream { fn write(&mut self, buf: &[u8]) -> io::Result { io::Write::write(&mut &*self, buf) @@ -438,7 +438,7 @@ impl io::Write for UnixStream { } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl<'a> io::Write for &'a UnixStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) @@ -449,21 +449,21 @@ impl<'a> io::Write for &'a UnixStream { } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl AsRawFd for UnixStream { fn as_raw_fd(&self) -> RawFd { self.0.raw() } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl FromRawFd for UnixStream { unsafe fn from_raw_fd(fd: RawFd) -> UnixStream { UnixStream(FileDesc::new(fd)) } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl IntoRawFd for UnixStream { fn into_raw_fd(self) -> RawFd { self.0.into_raw() @@ -498,10 +498,10 @@ impl IntoRawFd for UnixStream { /// } /// } /// ``` -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] pub struct UnixListener(FileDesc); -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl fmt::Debug for UnixListener { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut builder = fmt.debug_struct("UnixListener"); @@ -529,7 +529,7 @@ impl UnixListener { /// } /// }; /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn bind>(path: P) -> io::Result { if let Some(s) = path.as_ref().to_str() { cvt(syscall::open(format!("chan:{}", s), syscall::O_CREAT | syscall::O_CLOEXEC)) @@ -563,7 +563,7 @@ impl UnixListener { /// Err(e) => println!("accept function failed: {:?}", e), /// } /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { self.0.duplicate_path(b"listen").map(|fd| (UnixStream(fd), SocketAddr(()))) } @@ -583,7 +583,7 @@ impl UnixListener { /// /// let listener_copy = listener.try_clone().expect("try_clone failed"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn try_clone(&self) -> io::Result { self.0.duplicate().map(UnixListener) } @@ -599,7 +599,7 @@ impl UnixListener { /// /// let addr = listener.local_addr().expect("Couldn't get local address"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn local_addr(&self) -> io::Result { Err(Error::new(ErrorKind::Other, "UnixListener::local_addr unimplemented on redox")) } @@ -615,7 +615,7 @@ impl UnixListener { /// /// listener.set_nonblocking(true).expect("Couldn't set non blocking"); /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { self.0.set_nonblocking(nonblocking) } @@ -636,7 +636,7 @@ impl UnixListener { /// /// # Platform specific /// On Redox this always returns `None`. - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn take_error(&self) -> io::Result> { Ok(None) } @@ -672,34 +672,34 @@ impl UnixListener { /// } /// } /// ``` - #[stable(feature = "unix_socket_redox", since = "1.29")] + #[stable(feature = "unix_socket_redox", since = "1.29.0")] pub fn incoming<'a>(&'a self) -> Incoming<'a> { Incoming { listener: self } } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl AsRawFd for UnixListener { fn as_raw_fd(&self) -> RawFd { self.0.raw() } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl FromRawFd for UnixListener { unsafe fn from_raw_fd(fd: RawFd) -> UnixListener { UnixListener(FileDesc::new(fd)) } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl IntoRawFd for UnixListener { fn into_raw_fd(self) -> RawFd { self.0.into_raw() } } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl<'a> IntoIterator for &'a UnixListener { type Item = io::Result; type IntoIter = Incoming<'a>; @@ -740,12 +740,12 @@ impl<'a> IntoIterator for &'a UnixListener { /// } /// ``` #[derive(Debug)] -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] pub struct Incoming<'a> { listener: &'a UnixListener, } -#[stable(feature = "unix_socket_redox", since = "1.29")] +#[stable(feature = "unix_socket_redox", since = "1.29.0")] impl<'a> Iterator for Incoming<'a> { type Item = io::Result; diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 012fcbdd8c8e2..2a1f3c4801406 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -109,15 +109,14 @@ macro_rules! declare_features { // stable (active). // // Note that the features should be grouped into internal/user-facing -// and then sorted by version inside those groups. -// FIXME(60361): Enforce ^-- with tidy. +// and then sorted by version inside those groups. This is inforced with tidy. // // N.B., `tools/tidy/src/features.rs` parses this information directly out of the // source, so take care when modifying it. declare_features! ( // ------------------------------------------------------------------------- - // Internal feature gates. + // feature-group-start: internal feature gates // ------------------------------------------------------------------------- // no tracking issue START @@ -211,12 +210,12 @@ declare_features! ( // no tracking issue END - // Allows using the `may_dangle` attribute (RFC 1327). - (active, dropck_eyepatch, "1.10.0", Some(34761), None), - // Allows using `#[structural_match]` which indicates that a type is structurally matchable. (active, structural_match, "1.8.0", Some(31434), None), + // Allows using the `may_dangle` attribute (RFC 1327). + (active, dropck_eyepatch, "1.10.0", Some(34761), None), + // Allows using the `#![panic_runtime]` attribute. (active, panic_runtime, "1.10.0", Some(32837), None), @@ -252,7 +251,11 @@ declare_features! ( (active, test_2018_feature, "1.31.0", Some(0), Some(Edition::Edition2018)), // ------------------------------------------------------------------------- - // Actual feature gates (target features). + // feature-group-end: internal feature gates + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + // feature-group-start: actual feature gates (target features) // ------------------------------------------------------------------------- // FIXME: Document these and merge with the list below. @@ -275,7 +278,11 @@ declare_features! ( (active, f16c_target_feature, "1.36.0", Some(44839), None), // ------------------------------------------------------------------------- - // Actual feature gates. + // feature-group-end: actual feature gates (target features) + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + // feature-group-start: actual feature gates // ------------------------------------------------------------------------- // Allows using `asm!` macro with which inline assembly can be embedded. @@ -340,9 +347,6 @@ declare_features! ( // Permits specifying whether a function should permit unwinding or abort on unwind. (active, unwind_attributes, "1.4.0", Some(58760), None), - // Allows using `#[naked]` on functions. - (active, naked_functions, "1.9.0", Some(32408), None), - // Allows `#[no_debug]`. (active, no_debug, "1.5.0", Some(29721), None), @@ -358,6 +362,9 @@ declare_features! ( // Allows specialization of implementations (RFC 1210). (active, specialization, "1.7.0", Some(31844), None), + // Allows using `#[naked]` on functions. + (active, naked_functions, "1.9.0", Some(32408), None), + // Allows `cfg(target_has_atomic = "...")`. (active, cfg_target_has_atomic, "1.9.0", Some(32976), None), @@ -545,6 +552,10 @@ declare_features! ( // Allows using C-variadics. (active, c_variadic, "1.34.0", Some(44930), None), + + // ------------------------------------------------------------------------- + // feature-group-end: actual feature gates + // ------------------------------------------------------------------------- ); // Some features are known to be incomplete and using them is likely to have diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml index f7b491823f838..f5db2487618d6 100644 --- a/src/tools/tidy/Cargo.toml +++ b/src/tools/tidy/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Alex Crichton "] [dependencies] +regex = "1" serde = "1.0.8" serde_derive = "1.0.8" serde_json = "1.0.2" diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 8239fd9dce0c1..3144df6dd4cdf 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -7,6 +7,7 @@ //! * Library features have at most one stability level. //! * Library features have at most one `since` value. //! * All unstable lang features have tests to ensure they are actually unstable. +//! * Language features in a group are sorted by `since` value. use std::collections::HashMap; use std::fmt; @@ -14,6 +15,14 @@ use std::fs::{self, File}; use std::io::prelude::*; use std::path::Path; +use regex::{Regex, escape}; + +mod version; +use self::version::Version; + +const FEATURE_GROUP_START_PREFIX: &str = "// feature-group-start"; +const FEATURE_GROUP_END_PREFIX: &str = "// feature-group-end"; + #[derive(Debug, PartialEq, Clone)] pub enum Status { Stable, @@ -35,7 +44,7 @@ impl fmt::Display for Status { #[derive(Debug, Clone)] pub struct Feature { pub level: Status, - pub since: String, + pub since: Option, pub has_gate_test: bool, pub tracking_issue: Option, } @@ -129,20 +138,8 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) { } let mut lines = Vec::new(); - for (name, feature) in features.iter() { - lines.push(format!("{:<32} {:<8} {:<12} {:<8}", - name, - "lang", - feature.level, - feature.since)); - } - for (name, feature) in lib_features { - lines.push(format!("{:<32} {:<8} {:<12} {:<8}", - name, - "lib", - feature.level, - feature.since)); - } + lines.extend(format_features(&features, "lang")); + lines.extend(format_features(&lib_features, "lib")); lines.sort(); for line in lines { @@ -150,11 +147,31 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) { } } +fn format_features<'a>(features: &'a Features, family: &'a str) -> impl Iterator + 'a { + features.iter().map(move |(name, feature)| { + format!("{:<32} {:<8} {:<12} {:<8}", + name, + family, + feature.level, + feature.since.map_or("None".to_owned(), + |since| since.to_string())) + }) +} + fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> { - line.find(attr) - .and_then(|i| line[i..].find('"').map(|j| i + j + 1)) - .and_then(|i| line[i..].find('"').map(|j| (i, i + j))) - .map(|(i, j)| &line[i..j]) + let r = Regex::new(&format!(r#"{}\s*=\s*"([^"]*)""#, escape(attr))) + .expect("malformed regex for find_attr_val"); + r.captures(line) + .and_then(|c| c.get(1)) + .map(|m| m.as_str()) +} + +#[test] +fn test_find_attr_val() { + let s = r#"#[unstable(feature = "checked_duration_since", issue = "58402")]"#; + assert_eq!(find_attr_val(s, "feature"), Some("checked_duration_since")); + assert_eq!(find_attr_val(s, "issue"), Some("58402")); + assert_eq!(find_attr_val(s, "since"), None); } fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool { @@ -177,6 +194,9 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { // without one inside `// no tracking issue START` and `// no tracking issue END`. let mut next_feature_omits_tracking_issue = false; + let mut in_feature_group = false; + let mut prev_since = None; + contents.lines().zip(1..) .filter_map(|(line, line_number)| { let line = line.trim(); @@ -194,6 +214,25 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { _ => {} } + if line.starts_with(FEATURE_GROUP_START_PREFIX) { + if in_feature_group { + tidy_error!( + bad, + // ignore-tidy-linelength + "libsyntax/feature_gate.rs:{}: new feature group is started without ending the previous one", + line_number, + ); + } + + in_feature_group = true; + prev_since = None; + return None; + } else if line.starts_with(FEATURE_GROUP_END_PREFIX) { + in_feature_group = false; + prev_since = None; + return None; + } + let mut parts = line.split(','); let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) { Some("active") => Status::Unstable, @@ -202,7 +241,33 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { _ => return None, }; let name = parts.next().unwrap().trim(); - let since = parts.next().unwrap().trim().trim_matches('"'); + + let since_str = parts.next().unwrap().trim().trim_matches('"'); + let since = match since_str.parse() { + Ok(since) => Some(since), + Err(err) => { + tidy_error!( + bad, + "libsyntax/feature_gate.rs:{}: failed to parse since: {} ({:?})", + line_number, + since_str, + err, + ); + None + } + }; + if in_feature_group { + if prev_since > since { + tidy_error!( + bad, + "libsyntax/feature_gate.rs:{}: feature {} is not sorted by since", + line_number, + name, + ); + } + prev_since = since; + } + let issue_str = parts.next().unwrap().trim(); let tracking_issue = if issue_str.starts_with("None") { if level == Status::Unstable && !next_feature_omits_tracking_issue { @@ -222,7 +287,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { Some((name.to_owned(), Feature { level, - since: since.to_owned(), + since, has_gate_test: false, tracking_issue, })) @@ -239,7 +304,7 @@ pub fn collect_lib_features(base_src_path: &Path) -> Features { // add it to the set of known library features so we can still generate docs. lib_features.insert("compiler_builtins_lib".to_owned(), Feature { level: Status::Unstable, - since: String::new(), + since: None, has_gate_test: false, tracking_issue: None, }); @@ -336,11 +401,11 @@ fn map_lib_features(base_src_path: &Path, // `const fn` features are handled specially. let feature_name = match find_attr_val(line, "feature") { Some(name) => name, - None => err!("malformed stability attribute"), + None => err!("malformed stability attribute: missing `feature` key"), }; let feature = Feature { level: Status::Unstable, - since: "None".to_owned(), + since: None, has_gate_test: false, // FIXME(#57563): #57563 is now used as a common tracking issue, // although we would like to have specific tracking issues for each @@ -359,20 +424,23 @@ fn map_lib_features(base_src_path: &Path, }; let feature_name = match find_attr_val(line, "feature") { Some(name) => name, - None => err!("malformed stability attribute"), + None => err!("malformed stability attribute: missing `feature` key"), }; - let since = match find_attr_val(line, "since") { - Some(name) => name, + let since = match find_attr_val(line, "since").map(|x| x.parse()) { + Some(Ok(since)) => Some(since), + Some(Err(_err)) => { + err!("malformed stability attribute: can't parse `since` key"); + }, None if level == Status::Stable => { - err!("malformed stability attribute"); + err!("malformed stability attribute: missing the `since` key"); } - None => "None", + None => None, }; let tracking_issue = find_attr_val(line, "issue").map(|s| s.parse().unwrap()); let feature = Feature { level, - since: since.to_owned(), + since, has_gate_test: false, tracking_issue, }; diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs new file mode 100644 index 0000000000000..6027e7d35e28c --- /dev/null +++ b/src/tools/tidy/src/features/version.rs @@ -0,0 +1,92 @@ +use std::str::FromStr; +use std::num::ParseIntError; +use std::fmt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Version { + parts: [u32; 3], +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad(&format!("{}.{}.{}", self.parts[0], self.parts[1], self.parts[2])) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseVersionError { + ParseIntError(ParseIntError), + WrongNumberOfParts, +} + +impl From for ParseVersionError { + fn from(err: ParseIntError) -> Self { + ParseVersionError::ParseIntError(err) + } +} + +impl FromStr for Version { + type Err = ParseVersionError; + + fn from_str(s: &str) -> Result { + let mut iter = s.split('.').map(|part| Ok(part.parse()?)); + + let parts = { + let mut part = || { + iter.next() + .unwrap_or(Err(ParseVersionError::WrongNumberOfParts)) + }; + + [part()?, part()?, part()?] + }; + + if let Some(_) = iter.next() { + // Ensure we don't have more than 3 parts. + return Err(ParseVersionError::WrongNumberOfParts); + } + + Ok(Self { parts }) + } +} + +#[cfg(test)] +mod test { + use super::Version; + + #[test] + fn test_try_from_invalid_version() { + assert!("".parse::().is_err()); + assert!("hello".parse::().is_err()); + assert!("1.32.hi".parse::().is_err()); + assert!("1.32..1".parse::().is_err()); + assert!("1.32".parse::().is_err()); + assert!("1.32.0.1".parse::().is_err()); + } + + #[test] + fn test_try_from_single() { + assert_eq!("1.32.0".parse(), Ok(Version { parts: [1, 32, 0] })); + assert_eq!("1.0.0".parse(), Ok(Version { parts: [1, 0, 0] })); + } + + #[test] + fn test_compare() { + let v_1_0_0 = "1.0.0".parse::().unwrap(); + let v_1_32_0 = "1.32.0".parse::().unwrap(); + let v_1_32_1 = "1.32.1".parse::().unwrap(); + assert!(v_1_0_0 < v_1_32_1); + assert!(v_1_0_0 < v_1_32_0); + assert!(v_1_32_0 < v_1_32_1); + } + + #[test] + fn test_to_string() { + let v_1_0_0 = "1.0.0".parse::().unwrap(); + let v_1_32_1 = "1.32.1".parse::().unwrap(); + + assert_eq!(v_1_0_0.to_string(), "1.0.0"); + assert_eq!(v_1_32_1.to_string(), "1.32.1"); + assert_eq!(format!("{:<8}", v_1_32_1), "1.32.1 "); + assert_eq!(format!("{:>8}", v_1_32_1), " 1.32.1"); + } +} diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index c4a1246ffdf55..30080452edc01 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -5,6 +5,7 @@ #![deny(rust_2018_idioms)] +extern crate regex; extern crate serde_json; #[macro_use] extern crate serde_derive;