diff --git a/Cargo.lock b/Cargo.lock index fff30b0f27b..d552bb655b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,7 +147,21 @@ dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", - "anstyle-wincon", + "anstyle-wincon 2.1.0", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon 3.0.2", "colorchoice", "utf8parse", ] @@ -186,6 +200,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -520,7 +544,7 @@ version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" dependencies = [ - "anstream", + "anstream 0.5.0", "anstyle", "clap_lex", "strsim", @@ -558,7 +582,7 @@ checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" name = "clippy" version = "0.1.78" dependencies = [ - "anstream", + "anstream 0.5.0", "clippy_config", "clippy_lints", "clippy_utils", @@ -1234,6 +1258,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -1247,6 +1281,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" +dependencies = [ + "anstream 0.6.11", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.0" @@ -1638,9 +1685,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.3.7" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" dependencies = [ "log", "pest", @@ -2227,6 +2274,7 @@ dependencies = [ name = "linkchecker" version = "0.1.0" dependencies = [ + "html5ever", "once_cell", "regex", ] @@ -2335,9 +2383,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80992cb0e05f22cc052c99f8e883f1593b891014b96a8b4637fd274d7030c85e" +checksum = "0c33564061c3c640bed5ace7d6a2a1b65f2c64257d1ac930c15e94ed0fb561d3" dependencies = [ "ammonia", "anyhow", @@ -2345,14 +2393,13 @@ dependencies = [ "clap", "clap_complete", "elasticlunr-rs", - "env_logger", + "env_logger 0.11.1", "handlebars", "log", "memchr", "once_cell", "opener", - "pathdiff", - "pulldown-cmark", + "pulldown-cmark 0.10.0", "regex", "serde", "serde_json", @@ -2471,7 +2518,7 @@ dependencies = [ "aes", "colored", "ctrlc", - "env_logger", + "env_logger 0.10.0", "getrandom", "jemalloc-sys", "lazy_static", @@ -2689,7 +2736,7 @@ dependencies = [ "camino", "clap", "derive_builder", - "env_logger", + "env_logger 0.10.0", "fs_extra", "glob", "humansize", @@ -3012,6 +3059,24 @@ dependencies = [ "unicase", ] +[[package]] +name = "pulldown-cmark" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7" +dependencies = [ + "bitflags 2.4.1", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d8f9aa0e3cbcfaf8bf00300004ee3b72f74770f9cbac93f6928771f613276b" + [[package]] name = "punycode" version = "0.4.1" @@ -3271,7 +3336,7 @@ name = "rustbook" version = "0.1.0" dependencies = [ "clap", - "env_logger", + "env_logger 0.10.0", "mdbook", ] @@ -4427,7 +4492,7 @@ name = "rustc_resolve" version = "0.0.0" dependencies = [ "bitflags 2.4.1", - "pulldown-cmark", + "pulldown-cmark 0.9.6", "rustc_arena", "rustc_ast", "rustc_ast_pretty", @@ -4971,9 +5036,9 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" diff --git a/library/alloc/src/fmt.rs b/library/alloc/src/fmt.rs index 5b50ef7bf6c..fce2585cbf5 100644 --- a/library/alloc/src/fmt.rs +++ b/library/alloc/src/fmt.rs @@ -188,6 +188,10 @@ //! * `#X` - precedes the argument with a `0x` //! * `#b` - precedes the argument with a `0b` //! * `#o` - precedes the argument with a `0o` +//! +//! See [Formatting traits](#formatting-traits) for a description of what the `?`, `x`, `X`, +//! `b`, and `o` flags do. +//! //! * `0` - This is used to indicate for integer formats that the padding to `width` should //! both be done with a `0` character as well as be sign-aware. A format //! like `{:08}` would yield `00000001` for the integer `1`, while the @@ -197,6 +201,7 @@ //! and before the digits. When used together with the `#` flag, a similar //! rule applies: padding zeros are inserted after the prefix but before //! the digits. The prefix is included in the total width. +//! This flag overrides the [fill character and alignment flag](#fillalignment). //! //! ## Precision //! diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index bb839a71e90..248943cf022 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -88,6 +88,7 @@ use crate::str; // want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under // `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. #[cfg_attr(not(doc), repr(transparent))] +#[allow(clippy::derived_hash_with_manual_eq)] pub struct CStr { // FIXME: this should not be represented with a DST slice but rather with // just a raw `c_char` along with some form of marker to make diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 709eba2ffc9..047cb64ce50 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -462,6 +462,7 @@ impl f32 { /// and target platforms isn't guaranteed. #[stable(feature = "assoc_int_consts", since = "1.43.0")] #[rustc_diagnostic_item = "f32_nan"] + #[allow(clippy::eq_op)] pub const NAN: f32 = 0.0_f32 / 0.0_f32; /// Infinity (∞). #[stable(feature = "assoc_int_consts", since = "1.43.0")] @@ -483,6 +484,7 @@ impl f32 { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] #[inline] + #[allow(clippy::eq_op)] // > if you intended to check if the operand is NaN, use `.is_nan()` instead :) pub const fn is_nan(self) -> bool { self != self } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 73fa61574cc..16d81941935 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -461,6 +461,7 @@ impl f64 { /// and target platforms isn't guaranteed. #[rustc_diagnostic_item = "f64_nan"] #[stable(feature = "assoc_int_consts", since = "1.43.0")] + #[allow(clippy::eq_op)] pub const NAN: f64 = 0.0_f64 / 0.0_f64; /// Infinity (∞). #[stable(feature = "assoc_int_consts", since = "1.43.0")] @@ -482,6 +483,7 @@ impl f64 { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] #[inline] + #[allow(clippy::eq_op)] // > if you intended to check if the operand is NaN, use `.is_nan()` instead :) pub const fn is_nan(self) -> bool { self != self } diff --git a/library/core/src/option.rs b/library/core/src/option.rs index d4c40c49934..0e5eb03239c 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -567,6 +567,7 @@ use crate::{ #[rustc_diagnostic_item = "Option"] #[lang = "Option"] #[stable(feature = "rust1", since = "1.0.0")] +#[allow(clippy::derived_hash_with_manual_eq)] // PartialEq is specialized pub enum Option { /// No value. #[lang = "None"] diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 0fb9017e6d9..1c2c9901e52 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -376,6 +376,8 @@ //! [Stacked Borrows]: https://plv.mpi-sws.org/rustbelt/stacked-borrows/ #![stable(feature = "rust1", since = "1.0.0")] +// There are many unsafe functions taking pointers that don't dereference them. +#![allow(clippy::not_unsafe_ptr_arg_deref)] use crate::cmp::Ordering; use crate::fmt; diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index d9654973b84..1dec2bf40cc 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -216,6 +216,10 @@ #![cfg_attr(not(target_has_atomic_load_store = "8"), allow(dead_code))] #![cfg_attr(not(target_has_atomic_load_store = "8"), allow(unused_imports))] #![rustc_diagnostic_item = "atomic_mod"] +// Clippy complains about the pattern of "safe function calling unsafe function taking pointers". +// This happens with AtomicPtr intrinsics but is fine, as the pointers clippy is concerned about +// are just normal values that get loaded/stored, but not dereferenced. +#![allow(clippy::not_unsafe_ptr_arg_deref)] use self::Ordering::*; diff --git a/library/core/src/time.rs b/library/core/src/time.rs index e4f29942966..b533f539938 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -28,6 +28,14 @@ const NANOS_PER_MILLI: u32 = 1_000_000; const NANOS_PER_MICRO: u32 = 1_000; const MILLIS_PER_SEC: u64 = 1_000; const MICROS_PER_SEC: u64 = 1_000_000; +#[unstable(feature = "duration_units", issue = "120301")] +const SECS_PER_MINUTE: u64 = 60; +#[unstable(feature = "duration_units", issue = "120301")] +const MINS_PER_HOUR: u64 = 60; +#[unstable(feature = "duration_units", issue = "120301")] +const HOURS_PER_DAY: u64 = 24; +#[unstable(feature = "duration_units", issue = "120301")] +const DAYS_PER_WEEK: u64 = 7; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] @@ -273,6 +281,11 @@ impl Duration { /// Creates a new `Duration` from the specified number of nanoseconds. /// + /// Note: Using this on the return value of `as_nanos()` might cause unexpected behavior: + /// `as_nanos()` returns a u128, and can return values that do not fit in u64, e.g. 585 years. + /// Instead, consider using the pattern `Duration::new(d.as_secs(), d.subsec_nanos())` + /// if you cannot copy/clone the Duration directly. + /// /// # Examples /// /// ``` @@ -291,6 +304,118 @@ impl Duration { Duration::new(nanos / (NANOS_PER_SEC as u64), (nanos % (NANOS_PER_SEC as u64)) as u32) } + /// Creates a new `Duration` from the specified number of weeks. + /// + /// # Panics + /// + /// Panics if the given number of weeks overflows the `Duration` size. + /// + /// # Examples + /// + /// ``` + /// #![feature(duration_constructors)] + /// use std::time::Duration; + /// + /// let duration = Duration::from_weeks(4); + /// + /// assert_eq!(4 * 7 * 24 * 60 * 60, duration.as_secs()); + /// assert_eq!(0, duration.subsec_nanos()); + /// ``` + #[unstable(feature = "duration_constructors", issue = "120301")] + #[must_use] + #[inline] + pub const fn from_weeks(weeks: u64) -> Duration { + if weeks > u64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK) { + panic!("overflow in Duration::from_days"); + } + + Duration::from_secs(weeks * MINS_PER_HOUR * SECS_PER_MINUTE * HOURS_PER_DAY * DAYS_PER_WEEK) + } + + /// Creates a new `Duration` from the specified number of days. + /// + /// # Panics + /// + /// Panics if the given number of days overflows the `Duration` size. + /// + /// # Examples + /// + /// ``` + /// #![feature(duration_constructors)] + /// use std::time::Duration; + /// + /// let duration = Duration::from_days(7); + /// + /// assert_eq!(7 * 24 * 60 * 60, duration.as_secs()); + /// assert_eq!(0, duration.subsec_nanos()); + /// ``` + #[unstable(feature = "duration_constructors", issue = "120301")] + #[must_use] + #[inline] + pub const fn from_days(days: u64) -> Duration { + if days > u64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR * HOURS_PER_DAY) { + panic!("overflow in Duration::from_days"); + } + + Duration::from_secs(days * MINS_PER_HOUR * SECS_PER_MINUTE * HOURS_PER_DAY) + } + + /// Creates a new `Duration` from the specified number of hours. + /// + /// # Panics + /// + /// Panics if the given number of hours overflows the `Duration` size. + /// + /// # Examples + /// + /// ``` + /// #![feature(duration_constructors)] + /// use std::time::Duration; + /// + /// let duration = Duration::from_hours(6); + /// + /// assert_eq!(6 * 60 * 60, duration.as_secs()); + /// assert_eq!(0, duration.subsec_nanos()); + /// ``` + #[unstable(feature = "duration_constructors", issue = "120301")] + #[must_use] + #[inline] + pub const fn from_hours(hours: u64) -> Duration { + if hours > u64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR) { + panic!("overflow in Duration::from_hours"); + } + + Duration::from_secs(hours * MINS_PER_HOUR * SECS_PER_MINUTE) + } + + /// Creates a new `Duration` from the specified number of minutes. + /// + /// # Panics + /// + /// Panics if the given number of minutes overflows the `Duration` size. + /// + /// # Examples + /// + /// ``` + /// #![feature(duration_constructors)] + /// use std::time::Duration; + /// + /// let duration = Duration::from_mins(10); + /// + /// assert_eq!(10 * 60, duration.as_secs()); + /// assert_eq!(0, duration.subsec_nanos()); + /// ``` + #[unstable(feature = "duration_constructors", issue = "120301")] + #[must_use] + #[inline] + pub const fn from_mins(mins: u64) -> Duration { + if mins > u64::MAX / SECS_PER_MINUTE { + panic!("overflow in Duration::from_mins"); + } + + Duration::from_secs(mins * SECS_PER_MINUTE) + } + /// Returns true if this `Duration` spans no time. /// /// # Examples diff --git a/library/core/src/tuple.rs b/library/core/src/tuple.rs index 44fac589d4c..8e961d8adc3 100644 --- a/library/core/src/tuple.rs +++ b/library/core/src/tuple.rs @@ -154,18 +154,6 @@ macro_rules! maybe_tuple_doc { }; } -#[inline] -const fn ordering_is_some(c: Option, x: Ordering) -> bool { - // FIXME: Just use `==` once that's const-stable on `Option`s. - // This is mapping `None` to 2 and then doing the comparison afterwards - // because it optimizes better (`None::` is represented as 2). - x as i8 - == match c { - Some(c) => c as i8, - None => 2, - } -} - // Constructs an expression that performs a lexical ordering using method `$rel`. // The values are interleaved, so the macro invocation for // `(a1, a2, a3) < (b1, b2, b3)` would be `lexical_ord!(lt, opt_is_lt, a1, b1, @@ -176,7 +164,7 @@ const fn ordering_is_some(c: Option, x: Ordering) -> bool { macro_rules! lexical_ord { ($rel: ident, $ne_rel: ident, $a:expr, $b:expr, $($rest_a:expr, $rest_b:expr),+) => {{ let c = PartialOrd::partial_cmp(&$a, &$b); - if !ordering_is_some(c, Equal) { ordering_is_some(c, $ne_rel) } + if c != Some(Equal) { c == Some($ne_rel) } else { lexical_ord!($rel, $ne_rel, $($rest_a, $rest_b),+) } }}; ($rel: ident, $ne_rel: ident, $a:expr, $b:expr) => { diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 6deebc0d263..2fe79650dbf 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -32,6 +32,7 @@ #![feature(duration_abs_diff)] #![feature(duration_consts_float)] #![feature(duration_constants)] +#![feature(duration_constructors)] #![feature(exact_size_is_empty)] #![feature(extern_types)] #![feature(flt2dec)] diff --git a/library/core/tests/time.rs b/library/core/tests/time.rs index 23f07bf84b3..fe7bb11c675 100644 --- a/library/core/tests/time.rs +++ b/library/core/tests/time.rs @@ -17,6 +17,49 @@ fn new_overflow() { let _ = Duration::new(u64::MAX, 1_000_000_000); } +#[test] +#[should_panic] +fn from_mins_overflow() { + let overflow = u64::MAX / 60 + 1; + let _ = Duration::from_mins(overflow); +} + +#[test] +#[should_panic] +fn from_hours_overflow() { + let overflow = u64::MAX / (60 * 60) + 1; + let _ = Duration::from_hours(overflow); +} + +#[test] +#[should_panic] +fn from_days_overflow() { + let overflow = u64::MAX / (24 * 60 * 60) + 1; + let _ = Duration::from_days(overflow); +} + +#[test] +#[should_panic] +fn from_weeks_overflow() { + let overflow = u64::MAX / (7 * 24 * 60 * 60) + 1; + let _ = Duration::from_weeks(overflow); +} + +#[test] +fn constructors() { + assert_eq!(Duration::from_weeks(1), Duration::from_secs(7 * 24 * 60 * 60)); + assert_eq!(Duration::from_weeks(0), Duration::ZERO); + + assert_eq!(Duration::from_days(1), Duration::from_secs(86_400)); + assert_eq!(Duration::from_days(0), Duration::ZERO); + + assert_eq!(Duration::from_hours(1), Duration::from_secs(3_600)); + assert_eq!(Duration::from_hours(0), Duration::ZERO); + + assert_eq!(Duration::from_mins(1), Duration::from_secs(60)); + assert_eq!(Duration::from_mins(0), Duration::ZERO); +} + #[test] fn secs() { assert_eq!(Duration::new(0, 0).as_secs(), 0); diff --git a/library/proc_macro/src/bridge/arena.rs b/library/proc_macro/src/bridge/arena.rs index fa72d2816eb..c2b046ae41e 100644 --- a/library/proc_macro/src/bridge/arena.rs +++ b/library/proc_macro/src/bridge/arena.rs @@ -102,6 +102,7 @@ impl Arena { } } + #[allow(clippy::mut_from_ref)] // arena allocator pub(crate) fn alloc_str<'a>(&'a self, string: &str) -> &'a mut str { let alloc = self.alloc_raw(string.len()); let bytes = MaybeUninit::write_slice(alloc, string.as_bytes()); diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 87e89a464bc..90b76cbc26e 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -201,6 +201,7 @@ impl ToString for TokenStream { /// `TokenTree::Punct`, or `TokenTree::Literal`. #[stable(feature = "proc_macro_lib", since = "1.15.0")] impl fmt::Display for TokenStream { + #[allow(clippy::recursive_format_impl)] // clippy doesn't see the specialization fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_string()) } @@ -776,6 +777,7 @@ impl ToString for TokenTree { /// `TokenTree::Punct`, or `TokenTree::Literal`. #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl fmt::Display for TokenTree { + #[allow(clippy::recursive_format_impl)] // clippy doesn't see the specialization fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_string()) } @@ -906,6 +908,7 @@ impl ToString for Group { /// with `Delimiter::None` delimiters. #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl fmt::Display for Group { + #[allow(clippy::recursive_format_impl)] // clippy doesn't see the specialization fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_string()) } diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 24f2bdcf421..a4c2dc8b1ed 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -288,6 +288,7 @@ impl AsFd for fs::File { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`File`](fs::File)'s underlying file descriptor. #[inline] fn from(file: fs::File) -> OwnedFd { file.into_inner().into_inner().into_inner() @@ -296,6 +297,8 @@ impl From for OwnedFd { #[stable(feature = "io_safety", since = "1.63.0")] impl From for fs::File { + /// Returns a [`File`](fs::File) that takes ownership of the given + /// file descriptor. #[inline] fn from(owned_fd: OwnedFd) -> Self { Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned_fd))) @@ -312,6 +315,7 @@ impl AsFd for crate::net::TcpStream { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`TcpStream`](crate::net::TcpStream)'s socket file descriptor. #[inline] fn from(tcp_stream: crate::net::TcpStream) -> OwnedFd { tcp_stream.into_inner().into_socket().into_inner().into_inner().into() @@ -338,6 +342,7 @@ impl AsFd for crate::net::TcpListener { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`TcpListener`](crate::net::TcpListener)'s socket file descriptor. #[inline] fn from(tcp_listener: crate::net::TcpListener) -> OwnedFd { tcp_listener.into_inner().into_socket().into_inner().into_inner().into() @@ -364,6 +369,7 @@ impl AsFd for crate::net::UdpSocket { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`UdpSocket`](crate::net::UdpSocket)'s file descriptor. #[inline] fn from(udp_socket: crate::net::UdpSocket) -> OwnedFd { udp_socket.into_inner().into_socket().into_inner().into_inner().into() diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs index 34db54235f1..3b7b610fdf9 100644 --- a/library/std/src/os/unix/net/datagram.rs +++ b/library/std/src/os/unix/net/datagram.rs @@ -1024,6 +1024,7 @@ impl AsFd for UnixDatagram { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`UnixDatagram`]'s socket file descriptor. #[inline] fn from(unix_datagram: UnixDatagram) -> OwnedFd { unsafe { OwnedFd::from_raw_fd(unix_datagram.into_raw_fd()) } diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index ecc0bbce543..d64a43bc20b 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -346,6 +346,7 @@ impl From for UnixListener { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`UnixListener`]'s socket file descriptor. #[inline] fn from(listener: UnixListener) -> OwnedFd { listener.0.into_inner().into_inner() diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index 41290e0017a..e117f616caf 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -752,6 +752,7 @@ impl AsFd for UnixStream { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`UnixStream`]'s socket file descriptor. #[inline] fn from(unix_stream: UnixStream) -> OwnedFd { unsafe { OwnedFd::from_raw_fd(unix_stream.into_raw_fd()) } diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index ac551030492..e45457b2e42 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -362,6 +362,8 @@ impl FromRawFd for process::Stdio { #[stable(feature = "io_safety", since = "1.63.0")] impl From for process::Stdio { + /// Takes ownership of a file descriptor and returns a [`Stdio`](process::Stdio) + /// that can attach a stream to it. #[inline] fn from(fd: OwnedFd) -> process::Stdio { let fd = sys::fd::FileDesc::from_inner(fd); @@ -428,6 +430,7 @@ impl AsFd for crate::process::ChildStdin { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`ChildStdin`](crate::process::ChildStdin)'s file descriptor. #[inline] fn from(child_stdin: crate::process::ChildStdin) -> OwnedFd { child_stdin.into_inner().into_inner().into_inner() @@ -458,6 +461,7 @@ impl AsFd for crate::process::ChildStdout { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`ChildStdout`](crate::process::ChildStdout)'s file descriptor. #[inline] fn from(child_stdout: crate::process::ChildStdout) -> OwnedFd { child_stdout.into_inner().into_inner().into_inner() @@ -488,6 +492,7 @@ impl AsFd for crate::process::ChildStderr { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { + /// Takes ownership of a [`ChildStderr`](crate::process::ChildStderr)'s file descriptor. #[inline] fn from(child_stderr: crate::process::ChildStderr) -> OwnedFd { child_stderr.into_inner().into_inner().into_inner() diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index b0540872c0b..458c3bb036d 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -502,6 +502,7 @@ impl AsHandle for fs::File { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { + /// Takes ownership of a [`File`](fs::File)'s underlying file handle. #[inline] fn from(file: fs::File) -> OwnedHandle { file.into_inner().into_inner().into_inner() @@ -510,6 +511,7 @@ impl From for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl From for fs::File { + /// Returns a [`File`](fs::File) that takes ownership of the given handle. #[inline] fn from(owned: OwnedHandle) -> Self { Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) @@ -574,6 +576,7 @@ impl AsHandle for crate::process::ChildStdin { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { + /// Takes ownership of a [`ChildStdin`](crate::process::ChildStdin)'s file handle. #[inline] fn from(child_stdin: crate::process::ChildStdin) -> OwnedHandle { unsafe { OwnedHandle::from_raw_handle(child_stdin.into_raw_handle()) } @@ -590,6 +593,7 @@ impl AsHandle for crate::process::ChildStdout { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { + /// Takes ownership of a [`ChildStdout`](crate::process::ChildStdout)'s file handle. #[inline] fn from(child_stdout: crate::process::ChildStdout) -> OwnedHandle { unsafe { OwnedHandle::from_raw_handle(child_stdout.into_raw_handle()) } @@ -606,6 +610,7 @@ impl AsHandle for crate::process::ChildStderr { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { + /// Takes ownership of a [`ChildStderr`](crate::process::ChildStderr)'s file handle. #[inline] fn from(child_stderr: crate::process::ChildStderr) -> OwnedHandle { unsafe { OwnedHandle::from_raw_handle(child_stderr.into_raw_handle()) } diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index 65f161f32e7..6ffdf907c8e 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -319,6 +319,7 @@ impl AsSocket for crate::net::TcpStream { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedSocket { + /// Takes ownership of a [`TcpStream`](crate::net::TcpStream)'s socket. #[inline] fn from(tcp_stream: crate::net::TcpStream) -> OwnedSocket { unsafe { OwnedSocket::from_raw_socket(tcp_stream.into_raw_socket()) } @@ -343,6 +344,7 @@ impl AsSocket for crate::net::TcpListener { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedSocket { + /// Takes ownership of a [`TcpListener`](crate::net::TcpListener)'s socket. #[inline] fn from(tcp_listener: crate::net::TcpListener) -> OwnedSocket { unsafe { OwnedSocket::from_raw_socket(tcp_listener.into_raw_socket()) } @@ -367,6 +369,7 @@ impl AsSocket for crate::net::UdpSocket { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedSocket { + /// Takes ownership of a [`UdpSocket`](crate::net::UdpSocket)'s underlying socket. #[inline] fn from(udp_socket: crate::net::UdpSocket) -> OwnedSocket { unsafe { OwnedSocket::from_raw_socket(udp_socket.into_raw_socket()) } diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index 5bf0154eae3..1be3acf5d43 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -24,6 +24,8 @@ impl FromRawHandle for process::Stdio { #[stable(feature = "io_safety", since = "1.63.0")] impl From for process::Stdio { + /// Takes ownership of a handle and returns a [`Stdio`](process::Stdio) + /// that can attach a stream to it. fn from(handle: OwnedHandle) -> process::Stdio { let handle = sys::handle::Handle::from_inner(handle); let io = sys::process::Stdio::Handle(handle); @@ -56,6 +58,7 @@ impl IntoRawHandle for process::Child { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { + /// Takes ownership of a [`Child`](process::Child)'s process handle. fn from(child: process::Child) -> OwnedHandle { child.into_inner().into_handle().into_inner() } diff --git a/src/doc/unstable-book/src/library-features/duration-constructors.md b/src/doc/unstable-book/src/library-features/duration-constructors.md new file mode 100644 index 00000000000..098519c7c90 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/duration-constructors.md @@ -0,0 +1,9 @@ +# `duration_constructors` + +The tracking issue for this feature is: [#120301] + +[#120301]: https://github.com/rust-lang/rust/issues/120301 + +------------------------ + +Add the methods `from_mins`, `from_hours` and `from_days` to `Duration`. diff --git a/src/tools/linkchecker/Cargo.toml b/src/tools/linkchecker/Cargo.toml index 1d8f2f91882..318a69ab835 100644 --- a/src/tools/linkchecker/Cargo.toml +++ b/src/tools/linkchecker/Cargo.toml @@ -10,3 +10,4 @@ path = "main.rs" [dependencies] regex = "1" once_cell = "1" +html5ever = "0.26.0" diff --git a/src/tools/linkchecker/main.rs b/src/tools/linkchecker/main.rs index 7f73cac63cb..f49c6e79f13 100644 --- a/src/tools/linkchecker/main.rs +++ b/src/tools/linkchecker/main.rs @@ -14,6 +14,12 @@ //! A few exceptions are allowed as there's known bugs in rustdoc, but this //! should catch the majority of "broken link" cases. +use html5ever::tendril::ByteTendril; +use html5ever::tokenizer::{ + BufferQueue, TagToken, Token, TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts, +}; +use once_cell::sync::Lazy; +use regex::Regex; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::env; @@ -23,9 +29,6 @@ use std::path::{Component, Path, PathBuf}; use std::rc::Rc; use std::time::Instant; -use once_cell::sync::Lazy; -use regex::Regex; - // Add linkcheck exceptions here // If at all possible you should use intra-doc links to avoid linkcheck issues. These // are cases where that does not work @@ -182,163 +185,10 @@ impl Checker { } }; - // Search for anything that's the regex 'href[ ]*=[ ]*".*?"' - with_attrs_in_source(&source, " href", |url, i, base| { - // Ignore external URLs - if url.starts_with("http:") - || url.starts_with("https:") - || url.starts_with("javascript:") - || url.starts_with("ftp:") - || url.starts_with("irc:") - || url.starts_with("data:") - || url.starts_with("mailto:") - { - report.links_ignored_external += 1; - return; - } - report.links_checked += 1; - let (url, fragment) = match url.split_once('#') { - None => (url, None), - Some((url, fragment)) => (url, Some(fragment)), - }; - // NB: the `splitn` always succeeds, even if the delimiter is not present. - let url = url.splitn(2, '?').next().unwrap(); - - // Once we've plucked out the URL, parse it using our base url and - // then try to extract a file path. - let mut path = file.to_path_buf(); - if !base.is_empty() || !url.is_empty() { - path.pop(); - for part in Path::new(base).join(url).components() { - match part { - Component::Prefix(_) | Component::RootDir => { - // Avoid absolute paths as they make the docs not - // relocatable by making assumptions on where the docs - // are hosted relative to the site root. - report.errors += 1; - println!( - "{}:{}: absolute path - {}", - pretty_path, - i + 1, - Path::new(base).join(url).display() - ); - return; - } - Component::CurDir => {} - Component::ParentDir => { - path.pop(); - } - Component::Normal(s) => { - path.push(s); - } - } - } - } - - let (target_pretty_path, target_entry) = self.load_file(&path, report); - let (target_source, target_ids) = match target_entry { - FileEntry::Missing => { - if is_exception(file, &target_pretty_path) { - report.links_ignored_exception += 1; - } else { - report.errors += 1; - println!( - "{}:{}: broken link - `{}`", - pretty_path, - i + 1, - target_pretty_path - ); - } - return; - } - FileEntry::Dir => { - // Links to directories show as directory listings when viewing - // the docs offline so it's best to avoid them. - report.errors += 1; - println!( - "{}:{}: directory link to `{}` \ - (directory links should use index.html instead)", - pretty_path, - i + 1, - target_pretty_path - ); - return; - } - FileEntry::OtherFile => return, - FileEntry::Redirect { target } => { - let t = target.clone(); - let (target, redir_entry) = self.load_file(&t, report); - match redir_entry { - FileEntry::Missing => { - report.errors += 1; - println!( - "{}:{}: broken redirect from `{}` to `{}`", - pretty_path, - i + 1, - target_pretty_path, - target - ); - return; - } - FileEntry::Redirect { target } => { - // Redirect to a redirect, this link checker - // currently doesn't support this, since it would - // require cycle checking, etc. - report.errors += 1; - println!( - "{}:{}: redirect from `{}` to `{}` \ - which is also a redirect (not supported)", - pretty_path, - i + 1, - target_pretty_path, - target.display() - ); - return; - } - FileEntry::Dir => { - report.errors += 1; - println!( - "{}:{}: redirect from `{}` to `{}` \ - which is a directory \ - (directory links should use index.html instead)", - pretty_path, - i + 1, - target_pretty_path, - target - ); - return; - } - FileEntry::OtherFile => return, - FileEntry::HtmlFile { source, ids } => (source, ids), - } - } - FileEntry::HtmlFile { source, ids } => (source, ids), - }; - - // Alright, if we've found an HTML file for the target link. If - // this is a fragment link, also check that the `id` exists. - if let Some(ref fragment) = fragment { - // Fragments like `#1-6` are most likely line numbers to be - // interpreted by javascript, so we're ignoring these - if fragment.splitn(2, '-').all(|f| f.chars().all(|c| c.is_numeric())) { - return; - } - - parse_ids(&mut target_ids.borrow_mut(), &pretty_path, target_source, report); - - if target_ids.borrow().contains(*fragment) { - return; - } - - if is_exception(file, &format!("#{}", fragment)) { - report.links_ignored_exception += 1; - } else { - report.errors += 1; - print!("{}:{}: broken link fragment ", pretty_path, i + 1); - println!("`#{}` pointing to `{}`", fragment, target_pretty_path); - }; - } - }); + let (base, urls) = get_urls(&source); + for (i, url) in urls { + self.check_url(file, &pretty_path, report, &base, i, &url); + } self.check_intra_doc_links(file, &pretty_path, &source, report); @@ -350,6 +200,159 @@ impl Checker { } } + fn check_url( + &mut self, + file: &Path, + pretty_path: &str, + report: &mut Report, + base: &Option, + i: u64, + url: &str, + ) { + // Ignore external URLs + if url.starts_with("http:") + || url.starts_with("https:") + || url.starts_with("javascript:") + || url.starts_with("ftp:") + || url.starts_with("irc:") + || url.starts_with("data:") + || url.starts_with("mailto:") + { + report.links_ignored_external += 1; + return; + } + report.links_checked += 1; + let (url, fragment) = match url.split_once('#') { + None => (url, None), + Some((url, fragment)) => (url, Some(fragment)), + }; + // NB: the `splitn` always succeeds, even if the delimiter is not present. + let url = url.splitn(2, '?').next().unwrap(); + + // Once we've plucked out the URL, parse it using our base url and + // then try to extract a file path. + let mut path = file.to_path_buf(); + if base.is_some() || !url.is_empty() { + let base = base.as_deref().unwrap_or(""); + path.pop(); + for part in Path::new(base).join(url).components() { + match part { + Component::Prefix(_) | Component::RootDir => { + // Avoid absolute paths as they make the docs not + // relocatable by making assumptions on where the docs + // are hosted relative to the site root. + report.errors += 1; + println!( + "{}:{}: absolute path - {}", + pretty_path, + i, + Path::new(base).join(url).display() + ); + return; + } + Component::CurDir => {} + Component::ParentDir => { + path.pop(); + } + Component::Normal(s) => { + path.push(s); + } + } + } + } + + let (target_pretty_path, target_entry) = self.load_file(&path, report); + let (target_source, target_ids) = match target_entry { + FileEntry::Missing => { + if is_exception(file, &target_pretty_path) { + report.links_ignored_exception += 1; + } else { + report.errors += 1; + println!("{}:{}: broken link - `{}`", pretty_path, i, target_pretty_path); + } + return; + } + FileEntry::Dir => { + // Links to directories show as directory listings when viewing + // the docs offline so it's best to avoid them. + report.errors += 1; + println!( + "{}:{}: directory link to `{}` \ + (directory links should use index.html instead)", + pretty_path, i, target_pretty_path + ); + return; + } + FileEntry::OtherFile => return, + FileEntry::Redirect { target } => { + let t = target.clone(); + let (target, redir_entry) = self.load_file(&t, report); + match redir_entry { + FileEntry::Missing => { + report.errors += 1; + println!( + "{}:{}: broken redirect from `{}` to `{}`", + pretty_path, i, target_pretty_path, target + ); + return; + } + FileEntry::Redirect { target } => { + // Redirect to a redirect, this link checker + // currently doesn't support this, since it would + // require cycle checking, etc. + report.errors += 1; + println!( + "{}:{}: redirect from `{}` to `{}` \ + which is also a redirect (not supported)", + pretty_path, + i, + target_pretty_path, + target.display() + ); + return; + } + FileEntry::Dir => { + report.errors += 1; + println!( + "{}:{}: redirect from `{}` to `{}` \ + which is a directory \ + (directory links should use index.html instead)", + pretty_path, i, target_pretty_path, target + ); + return; + } + FileEntry::OtherFile => return, + FileEntry::HtmlFile { source, ids } => (source, ids), + } + } + FileEntry::HtmlFile { source, ids } => (source, ids), + }; + + // Alright, if we've found an HTML file for the target link. If + // this is a fragment link, also check that the `id` exists. + if let Some(ref fragment) = fragment { + // Fragments like `#1-6` are most likely line numbers to be + // interpreted by javascript, so we're ignoring these + if fragment.splitn(2, '-').all(|f| f.chars().all(|c| c.is_numeric())) { + return; + } + + parse_ids(&mut target_ids.borrow_mut(), &pretty_path, target_source, report); + + if target_ids.borrow().contains(*fragment) { + return; + } + + if is_exception(file, &format!("#{}", fragment)) { + report.links_ignored_exception += 1; + } else { + report.errors += 1; + print!("{}:{}: broken link fragment ", pretty_path, i); + println!("`#{}` pointing to `{}`", fragment, target_pretty_path); + }; + } + } + fn check_intra_doc_links( &mut self, file: &Path, @@ -496,59 +499,93 @@ fn maybe_redirect(source: &str) -> Option { find_redirect(REDIRECT_RUSTDOC).or_else(|| find_redirect(REDIRECT_MDBOOK)) } -fn with_attrs_in_source(source: &str, attr: &str, mut f: F) { - let mut base = ""; - for (i, mut line) in source.lines().enumerate() { - while let Some(j) = line.find(attr) { - let rest = &line[j + attr.len()..]; - // The base tag should always be the first link in the document so - // we can get away with using one pass. - let is_base = line[..j].ends_with("(source: &str, sink: Sink) -> Sink { + let tendril: ByteTendril = source.as_bytes().into(); + let mut input = BufferQueue::new(); + input.push_back(tendril.try_reinterpret().unwrap()); - let rest = &rest[pos_equals + 1..]; + let mut tok = Tokenizer::new(sink, TokenizerOpts::default()); + let _ = tok.feed(&mut input); + assert!(input.is_empty()); + tok.end(); + tok.sink +} - let pos_quote = match rest.find(&['"', '\''][..]) { - Some(i) => i, - None => continue, - }; - let quote_delim = rest.as_bytes()[pos_quote] as char; +#[derive(Default)] +struct AttrCollector { + attr_name: &'static [u8], + base: Option, + found_attrs: Vec<(u64, String)>, + /// Tracks whether or not it is inside a