From 8b82f685a57d6a1a3567c2ca6e77efedbedefac2 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Mon, 29 Apr 2019 20:01:46 +0300 Subject: [PATCH 1/9] Make find_attr_val a little bit more precise `find_attr_val(&line, "since")` returns `Some(", issue = ")` when `line` is set to the following line: ``` [unstable(feature = "checked_duration_since", issue = "58402")] ``` Make `find_attr_val` use regex that is a little bit more precise (requires `=` after key name). It still does not handle all cases (e.g., extra leading chars in key name, or escaped quotes in value), but is good enough for now. --- Cargo.lock | 1 + src/tools/tidy/Cargo.toml | 1 + src/tools/tidy/src/features.rs | 19 +++++++++++++++---- src/tools/tidy/src/lib.rs | 1 + 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd6a7a5604edf..79ef359bdac5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3565,6 +3565,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/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..f2b17c7471109 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -14,6 +14,8 @@ use std::fs::{self, File}; use std::io::prelude::*; use std::path::Path; +use regex::{Regex, escape}; + #[derive(Debug, PartialEq, Clone)] pub enum Status { Stable, @@ -151,10 +153,19 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) { } 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#"{} *= *"([^"]*)""#, 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 { 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; From b7f55ca238a70f6738b14f1fded0fb9a47957343 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Mon, 29 Apr 2019 20:39:55 +0300 Subject: [PATCH 2/9] Assign group and parse since for Feature --- src/tools/tidy/src/features.rs | 62 +++++++++++++++++++----- src/tools/tidy/src/features/version.rs | 66 ++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 src/tools/tidy/src/features/version.rs diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index f2b17c7471109..fe2aa0fd70fe3 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -16,6 +16,12 @@ 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, @@ -37,9 +43,10 @@ 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, + pub group: Option, } pub type Features = HashMap; @@ -136,14 +143,16 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) { name, "lang", feature.level, - feature.since)); + feature.since.as_ref().map_or("None".to_owned(), + |since| since.to_string()))); } for (name, feature) in lib_features { lines.push(format!("{:<32} {:<8} {:<12} {:<8}", name, "lib", feature.level, - feature.since)); + feature.since.as_ref().map_or("None".to_owned(), + |since| since.to_string()))); } lines.sort(); @@ -188,6 +197,8 @@ 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 next_feature_group = None; + contents.lines().zip(1..) .filter_map(|(line, line_number)| { let line = line.trim(); @@ -205,6 +216,15 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { _ => {} } + if line.starts_with(FEATURE_GROUP_START_PREFIX) { + let group = line.trim_start_matches(FEATURE_GROUP_START_PREFIX).trim(); + next_feature_group = Some(group.to_owned()); + return None; + } else if line.starts_with(FEATURE_GROUP_END_PREFIX) { + next_feature_group = None; + return None; + } + let mut parts = line.split(','); let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) { Some("active") => Status::Unstable, @@ -213,7 +233,20 @@ 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 + } + }; 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 { @@ -233,9 +266,10 @@ 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, + group: next_feature_group.clone(), })) }) .collect() @@ -250,9 +284,10 @@ 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, + group: None, }); map_lib_features(base_src_path, @@ -351,12 +386,13 @@ fn map_lib_features(base_src_path: &Path, }; 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 // `rustc_const_unstable` in the future. tracking_issue: Some(57563), + group: None, }; mf(Ok((feature_name, feature)), file, i + 1); continue; @@ -372,20 +408,24 @@ fn map_lib_features(base_src_path: &Path, Some(name) => name, None => err!("malformed stability attribute"), }; - 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 since attribute"); + }, None if level == Status::Stable => { err!("malformed stability attribute"); } - 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, + group: None, }; if line.contains(']') { mf(Ok((feature_name, feature)), file, i + 1); diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs new file mode 100644 index 0000000000000..0bab1427be849 --- /dev/null +++ b/src/tools/tidy/src/features/version.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; +use std::num::ParseIntError; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Version { + parts: Vec, +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let x = self.parts.iter().map(|x| x.to_string()).collect::>().join("."); + f.pad(&x) + } +} + +impl FromStr for Version { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + let parts = s.split('.').map(|part| part.parse()).collect::>()?; + Ok(Version { 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()); + } + + #[test] + fn test_try_from_single() { + assert_eq!("1.32.0".parse(), Ok(Version { parts: vec![1, 32, 0] })); + assert_eq!("1.0.0".parse(), Ok(Version { parts: vec![1, 0, 0] })); + } + + #[test] + fn test_compare() { + let v_1_0_0 = "1.0.0".parse::().unwrap(); + let v_1_32 = "1.32".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); + assert!(v_1_32 < v_1_32_1); + } + + #[test] + fn test_to_string() { + let v_1_0_0 = "1.0.0".parse::().unwrap(); + let v_1_32 = "1.32".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.to_string(), "1.32"); + 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"); + } +} From d5ba6d4b3cef4f7f54295d3a475411cedf1254fb Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Mon, 29 Apr 2019 20:51:19 +0300 Subject: [PATCH 3/9] Ensure language features in group are sorted by since --- src/tools/tidy/src/features.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index fe2aa0fd70fe3..d014c43f3b3f2 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; @@ -198,6 +199,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { let mut next_feature_omits_tracking_issue = false; let mut next_feature_group = None; + let mut prev_since = None; contents.lines().zip(1..) .filter_map(|(line, line_number)| { @@ -219,9 +221,11 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { if line.starts_with(FEATURE_GROUP_START_PREFIX) { let group = line.trim_start_matches(FEATURE_GROUP_START_PREFIX).trim(); next_feature_group = Some(group.to_owned()); + prev_since = None; return None; } else if line.starts_with(FEATURE_GROUP_END_PREFIX) { next_feature_group = None; + prev_since = None; return None; } @@ -233,6 +237,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { _ => return None, }; let name = parts.next().unwrap().trim(); + let since_str = parts.next().unwrap().trim().trim_matches('"'); let since = match since_str.parse() { Ok(since) => Some(since), @@ -247,6 +252,18 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { None } }; + if next_feature_group.is_some() { + if prev_since > since { + tidy_error!( + bad, + "libsyntax/feature_gate.rs:{}: feature {} is not sorted by since", + line_number, + name, + ); + } + prev_since = since.clone(); + } + 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 { From d54477e97415c8c4931533f98ebf2704cd60e9b4 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Wed, 1 May 2019 22:01:01 +0300 Subject: [PATCH 4/9] Address review comments --- src/tools/tidy/src/features.rs | 57 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index d014c43f3b3f2..c7a109e050987 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -20,8 +20,8 @@ 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"; +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 { @@ -47,7 +47,6 @@ pub struct Feature { pub since: Option, pub has_gate_test: bool, pub tracking_issue: Option, - pub group: Option, } pub type Features = HashMap; @@ -139,22 +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.as_ref().map_or("None".to_owned(), - |since| since.to_string()))); - } - for (name, feature) in lib_features { - lines.push(format!("{:<32} {:<8} {:<12} {:<8}", - name, - "lib", - feature.level, - feature.since.as_ref().map_or("None".to_owned(), - |since| since.to_string()))); - } + lines.extend(format_features(&features, "lang")); + lines.extend(format_features(&lib_features, "lib")); lines.sort(); for line in lines { @@ -162,8 +147,19 @@ 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.as_ref().map_or("None".to_owned(), + |since| since.to_string())) + }) +} + fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> { - let r = Regex::new(&format!(r#"{} *= *"([^"]*)""#, escape(attr))) + 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)) @@ -219,6 +215,15 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { } if line.starts_with(FEATURE_GROUP_START_PREFIX) { + if next_feature_group.is_some() { + tidy_error!( + bad, + // ignore-tidy-linelength + "libsyntax/feature_gate.rs:{}: new feature group is started without ending the previous one", + line_number, + ); + } + let group = line.trim_start_matches(FEATURE_GROUP_START_PREFIX).trim(); next_feature_group = Some(group.to_owned()); prev_since = None; @@ -286,7 +291,6 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { since, has_gate_test: false, tracking_issue, - group: next_feature_group.clone(), })) }) .collect() @@ -304,7 +308,6 @@ pub fn collect_lib_features(base_src_path: &Path) -> Features { since: None, has_gate_test: false, tracking_issue: None, - group: None, }); map_lib_features(base_src_path, @@ -399,7 +402,7 @@ 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, @@ -409,7 +412,6 @@ fn map_lib_features(base_src_path: &Path, // although we would like to have specific tracking issues for each // `rustc_const_unstable` in the future. tracking_issue: Some(57563), - group: None, }; mf(Ok((feature_name, feature)), file, i + 1); continue; @@ -423,15 +425,15 @@ 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").map(|x| x.parse()) { Some(Ok(since)) => Some(since), Some(Err(_err)) => { - err!("malformed since attribute"); + 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, }; @@ -442,7 +444,6 @@ fn map_lib_features(base_src_path: &Path, since, has_gate_test: false, tracking_issue, - group: None, }; if line.contains(']') { mf(Ok((feature_name, feature)), file, i + 1); From 90d3fa223d82cee5430c71d117b6be2922a256b3 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Wed, 1 May 2019 22:15:00 +0300 Subject: [PATCH 5/9] Make tidy::version::Version a [u32; 3] --- src/libstd/sys/redox/ext/net.rs | 82 +++++++++++++------------- src/tools/tidy/src/features.rs | 2 +- src/tools/tidy/src/features/version.rs | 38 ++++++++---- 3 files changed, 69 insertions(+), 53 deletions(-) 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/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index c7a109e050987..afadedd8b6bbb 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -249,7 +249,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { Err(err) => { tidy_error!( bad, - "libsyntax/feature_gate.rs:{}: failed to parse since: {} ({})", + "libsyntax/feature_gate.rs:{}: failed to parse since: {} ({:?})", line_number, since_str, err, diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs index 0bab1427be849..7ea788a85180b 100644 --- a/src/tools/tidy/src/features/version.rs +++ b/src/tools/tidy/src/features/version.rs @@ -1,10 +1,11 @@ use std::str::FromStr; use std::num::ParseIntError; use std::fmt; +use std::convert::TryInto; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { - parts: Vec, + parts: [u32; 3], } impl fmt::Display for Version { @@ -14,12 +15,27 @@ impl fmt::Display for Version { } } +#[derive(Debug, PartialEq, Eq)] +pub enum ParseVersionError { + ParseIntError(ParseIntError), + // core::array::TryFromSlice is not exported from std, so we invent our own variant + WrongNumberOfParts +} + +impl From for ParseVersionError { + fn from(err: ParseIntError) -> Self { + ParseVersionError::ParseIntError(err) + } +} + impl FromStr for Version { - type Err = ParseIntError; + type Err = ParseVersionError; fn from_str(s: &str) -> Result { - let parts = s.split('.').map(|part| part.parse()).collect::>()?; - Ok(Version { parts }) + let parts: Vec<_> = s.split('.').map(|part| part.parse()).collect::>()?; + Ok(Self { + parts: parts.as_slice().try_into() .or(Err(ParseVersionError::WrongNumberOfParts))?, + }) } } @@ -33,32 +49,32 @@ mod test { 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: vec![1, 32, 0] })); - assert_eq!("1.0.0".parse(), Ok(Version { parts: vec![1, 0, 0] })); + 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 = "1.32".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); - assert!(v_1_32 < 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.32".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.to_string(), "1.32"); 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"); From 3b4fe7ef37c3ff2afcfda8577f010d3fe926bde5 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Wed, 1 May 2019 22:42:57 +0300 Subject: [PATCH 6/9] Group and sort feature_gate.rs --- src/libsyntax/feature_gate.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) 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 From c120fd823ba1cbd78167f0f3dca32b68d9f2308a Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Thu, 2 May 2019 00:52:44 +0300 Subject: [PATCH 7/9] Rework Version::parse to avoid extra allocations --- src/tools/tidy/src/features.rs | 2 +- src/tools/tidy/src/features/version.rs | 28 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index afadedd8b6bbb..48ecbe3380745 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -147,7 +147,7 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) { } } -fn format_features<'a>(features: &'a Features, family: &'a str) -> impl Iterator + 'a { +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, diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs index 7ea788a85180b..8d1ebccbff670 100644 --- a/src/tools/tidy/src/features/version.rs +++ b/src/tools/tidy/src/features/version.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use std::num::ParseIntError; use std::fmt; -use std::convert::TryInto; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { @@ -10,16 +9,14 @@ pub struct Version { impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let x = self.parts.iter().map(|x| x.to_string()).collect::>().join("."); - f.pad(&x) + f.pad(&format!("{}.{}.{}", self.parts[0], self.parts[1], self.parts[2])) } } #[derive(Debug, PartialEq, Eq)] pub enum ParseVersionError { ParseIntError(ParseIntError), - // core::array::TryFromSlice is not exported from std, so we invent our own variant - WrongNumberOfParts + WrongNumberOfParts, } impl From for ParseVersionError { @@ -32,10 +29,23 @@ impl FromStr for Version { type Err = ParseVersionError; fn from_str(s: &str) -> Result { - let parts: Vec<_> = s.split('.').map(|part| part.parse()).collect::>()?; - Ok(Self { - parts: parts.as_slice().try_into() .or(Err(ParseVersionError::WrongNumberOfParts))?, - }) + 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 }) } } From 4bcc828b9ca7611ecb1200056bab5fc6d805fd99 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Thu, 2 May 2019 14:30:30 +0300 Subject: [PATCH 8/9] Make in_feature_group a simple bool flag --- src/tools/tidy/src/features.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 48ecbe3380745..63731a0cb056b 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -20,7 +20,7 @@ use regex::{Regex, escape}; mod version; use self::version::Version; -const FEATURE_GROUP_START_PREFIX: &str = "// feature-group-start:"; +const FEATURE_GROUP_START_PREFIX: &str = "// feature-group-start"; const FEATURE_GROUP_END_PREFIX: &str = "// feature-group-end"; #[derive(Debug, PartialEq, Clone)] @@ -194,7 +194,7 @@ 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 next_feature_group = None; + let mut in_feature_group = false; let mut prev_since = None; contents.lines().zip(1..) @@ -215,7 +215,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { } if line.starts_with(FEATURE_GROUP_START_PREFIX) { - if next_feature_group.is_some() { + if in_feature_group { tidy_error!( bad, // ignore-tidy-linelength @@ -224,12 +224,11 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { ); } - let group = line.trim_start_matches(FEATURE_GROUP_START_PREFIX).trim(); - next_feature_group = Some(group.to_owned()); + in_feature_group = true; prev_since = None; return None; } else if line.starts_with(FEATURE_GROUP_END_PREFIX) { - next_feature_group = None; + in_feature_group = false; prev_since = None; return None; } @@ -257,7 +256,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { None } }; - if next_feature_group.is_some() { + if in_feature_group { if prev_since > since { tidy_error!( bad, From 201f14b88b19d43615845bfc2a6de9bc31985b13 Mon Sep 17 00:00:00 2001 From: Alexey Shmalko Date: Thu, 2 May 2019 14:38:16 +0300 Subject: [PATCH 9/9] Make tidy::version::Version copy --- src/tools/tidy/src/features.rs | 6 +++--- src/tools/tidy/src/features/version.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 63731a0cb056b..3144df6dd4cdf 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -153,8 +153,8 @@ fn format_features<'a>(features: &'a Features, family: &'a str) -> impl Iterator name, family, feature.level, - feature.since.as_ref().map_or("None".to_owned(), - |since| since.to_string())) + feature.since.map_or("None".to_owned(), + |since| since.to_string())) }) } @@ -265,7 +265,7 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features { name, ); } - prev_since = since.clone(); + prev_since = since; } let issue_str = parts.next().unwrap().trim(); diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs index 8d1ebccbff670..6027e7d35e28c 100644 --- a/src/tools/tidy/src/features/version.rs +++ b/src/tools/tidy/src/features/version.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::num::ParseIntError; use std::fmt; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { parts: [u32; 3], }