From 994e712162af1d568a1751c32626478f2c613cc2 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 16 Jan 2023 17:56:01 +0100 Subject: [PATCH 001/685] Implement DoubleEnded and ExactSize for Take and Take MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repeat iterator always returns the same element and behaves the same way backwards and forwards. Take iterator can trivially implement backwards iteration over Repeat inner iterator by simply doing forwards iteration. DoubleEndedIterator is not currently implemented for Take> because Repeat doesn’t implement ExactSizeIterator which is a required bound on DEI implementation for Take. Similarly, since Repeat is an infinite iterator which never stops, Take can trivially know how many elements it’s going to return. This allows implementing ExactSizeIterator on Take>. While at it, observe that ExactSizeIterator can also be implemented for Take> so add that implementation too. Since in contrast to Repeat, RepeatWhile doesn’t guarante to always return the same value, DoubleEndedIterator isn’t implemented. Those changes render core::iter::repeat_n somewhat redundant. Issue: https://github.com/rust-lang/rust/issues/104434 Issue: https://github.com/rust-lang/rust/issues/104729 --- library/core/src/iter/adapters/take.rs | 57 +++++++++++++++ library/core/tests/iter/adapters/take.rs | 90 ++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/library/core/src/iter/adapters/take.rs b/library/core/src/iter/adapters/take.rs index 297dd0acaddc1..4c8f9fe16da0f 100644 --- a/library/core/src/iter/adapters/take.rs +++ b/library/core/src/iter/adapters/take.rs @@ -317,3 +317,60 @@ impl SpecTake for Take { } } } + +#[stable(feature = "exact_size_take_repeat", since = "CURRENT_RUSTC_VERSION")] +impl DoubleEndedIterator for Take> { + #[inline] + fn next_back(&mut self) -> Option { + self.next() + } + + #[inline] + fn nth_back(&mut self, n: usize) -> Option { + self.nth(n) + } + + #[inline] + fn try_rfold(&mut self, init: Acc, fold: Fold) -> R + where + Self: Sized, + Fold: FnMut(Acc, Self::Item) -> R, + R: Try, + { + self.try_fold(init, fold) + } + + #[inline] + fn rfold(self, init: Acc, fold: Fold) -> Acc + where + Self: Sized, + Fold: FnMut(Acc, Self::Item) -> Acc, + { + self.fold(init, fold) + } + + #[inline] + #[rustc_inherit_overflow_checks] + fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { + self.advance_by(n) + } +} + +// Note: It may be tempting to impl DoubleEndedIterator for Take. +// One must fight that temptation since such implementation wouldn’t be correct +// because we have no way to return value of nth invocation of repeater followed +// by n-1st without remembering all results. + +#[stable(feature = "exact_size_take_repeat", since = "CURRENT_RUSTC_VERSION")] +impl ExactSizeIterator for Take> { + fn len(&self) -> usize { + self.n + } +} + +#[stable(feature = "exact_size_take_repeat", since = "CURRENT_RUSTC_VERSION")] +impl A, A> ExactSizeIterator for Take> { + fn len(&self) -> usize { + self.n + } +} diff --git a/library/core/tests/iter/adapters/take.rs b/library/core/tests/iter/adapters/take.rs index 39afa2cbfcaf2..65a8a93b4a916 100644 --- a/library/core/tests/iter/adapters/take.rs +++ b/library/core/tests/iter/adapters/take.rs @@ -170,3 +170,93 @@ fn test_byref_take_consumed_items() { assert_eq!(count, 70); assert_eq!(inner, 90..90); } + +#[test] +fn test_exact_size_take_repeat() { + let mut iter = core::iter::repeat(42).take(40); + assert_eq!((40, Some(40)), iter.size_hint()); + assert_eq!(40, iter.len()); + + assert_eq!(Some(42), iter.next()); + assert_eq!((39, Some(39)), iter.size_hint()); + assert_eq!(39, iter.len()); + + assert_eq!(Some(42), iter.next_back()); + assert_eq!((38, Some(38)), iter.size_hint()); + assert_eq!(38, iter.len()); + + assert_eq!(Some(42), iter.nth(3)); + assert_eq!((34, Some(34)), iter.size_hint()); + assert_eq!(34, iter.len()); + + assert_eq!(Some(42), iter.nth_back(3)); + assert_eq!((30, Some(30)), iter.size_hint()); + assert_eq!(30, iter.len()); + + assert_eq!(Ok(()), iter.advance_by(10)); + assert_eq!((20, Some(20)), iter.size_hint()); + assert_eq!(20, iter.len()); + + assert_eq!(Ok(()), iter.advance_back_by(10)); + assert_eq!((10, Some(10)), iter.size_hint()); + assert_eq!(10, iter.len()); +} + +#[test] +fn test_exact_size_take_repeat_with() { + let mut counter = 0; + let mut iter = core::iter::repeat_with(move || { + counter += 1; + counter + }) + .take(40); + assert_eq!((40, Some(40)), iter.size_hint()); + assert_eq!(40, iter.len()); + + assert_eq!(Some(1), iter.next()); + assert_eq!((39, Some(39)), iter.size_hint()); + assert_eq!(39, iter.len()); + + assert_eq!(Some(5), iter.nth(3)); + assert_eq!((35, Some(35)), iter.size_hint()); + assert_eq!(35, iter.len()); + + assert_eq!(Ok(()), iter.advance_by(10)); + assert_eq!((25, Some(25)), iter.size_hint()); + assert_eq!(25, iter.len()); + + assert_eq!(Some(16), iter.next()); + assert_eq!((24, Some(24)), iter.size_hint()); + assert_eq!(24, iter.len()); +} + +// This is https://github.com/rust-lang/rust/issues/104729 with all uses of +// repeat(0) were replaced by repeat(0).take(20). +#[test] +fn test_reverse_on_zip() { + let vec_1 = [1; 10]; + + let zipped_iter = vec_1.iter().copied().zip(core::iter::repeat(0).take(20)); + + // Forward + for (one, zero) in zipped_iter { + assert_eq!((1, 0), (one, zero)); + } + + let rev_vec_iter = vec_1.iter().rev(); + let rev_repeat_iter = std::iter::repeat(0).take(20).rev(); + + // Manual reversed zip + let rev_zipped_iter = rev_vec_iter.zip(rev_repeat_iter); + + for (&one, zero) in rev_zipped_iter { + assert_eq!((1, 0), (one, zero)); + } + + let zipped_iter = vec_1.iter().zip(core::iter::repeat(0).take(20)); + + // Cannot call rev here for automatic reversed zip constuction + for (&one, zero) in zipped_iter.rev() { + assert_eq!((1, 0), (one, zero)); + } +} From 07481b9e90df0860519043b54e07529003e74814 Mon Sep 17 00:00:00 2001 From: bohan Date: Fri, 5 Jul 2024 00:59:58 +0800 Subject: [PATCH 002/685] use old ctx if has same expand environment during decode span --- compiler/rustc_span/src/hygiene.rs | 8 +++++++ tests/incremental/decl_macro.rs | 34 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/incremental/decl_macro.rs diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 483e32c645393..7e6acc0b9fba9 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1413,6 +1413,14 @@ pub fn decode_syntax_context SyntaxContext // Overwrite the dummy data with our decoded SyntaxContextData HygieneData::with(|hygiene_data| { + if let Some(old) = hygiene_data.syntax_context_data.get(raw_id as usize) + && old.outer_expn == ctxt_data.outer_expn + && old.outer_transparency == ctxt_data.outer_transparency + && old.parent == ctxt_data.parent + { + ctxt_data = old.clone(); + } + let dummy = std::mem::replace( &mut hygiene_data.syntax_context_data[ctxt.as_u32() as usize], ctxt_data, diff --git a/tests/incremental/decl_macro.rs b/tests/incremental/decl_macro.rs new file mode 100644 index 0000000000000..74810ae422749 --- /dev/null +++ b/tests/incremental/decl_macro.rs @@ -0,0 +1,34 @@ +//@ revisions: rpass1 rpass2 + +// issue#112680 + +#![feature(decl_macro)] + +pub trait T { + type Key; + fn index_from_key(key: Self::Key) -> usize; +} + +pub macro m($key_ty:ident, $val_ty:ident) { + struct $key_ty { + inner: usize, + } + + impl T for $val_ty { + type Key = $key_ty; + + fn index_from_key(key: Self::Key) -> usize { + key.inner + } + } +} + +m!(TestId, Test); + +#[cfg(rpass1)] +struct Test(u32); + +#[cfg(rpass2)] +struct Test; + +fn main() {} From df27dfa0eae8a14d69e5006334bb06d84ba050b7 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 22 Mar 2024 16:50:48 +0200 Subject: [PATCH 003/685] Optimize integer pow by removing exit branch The branch at the end of the `pow` implementations is redundant with multiplication code already present in the loop. By rotating the exit check, this branch can be largely removed, improving code size and instruction cache coherence. --- library/core/src/num/int_macros.rs | 61 ++++++++++++--------------- library/core/src/num/uint_macros.rs | 64 ++++++++++++----------------- 2 files changed, 52 insertions(+), 73 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index d40e02352a1d0..6ed0eb07e48d8 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1495,18 +1495,17 @@ macro_rules! int_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = try_opt!(acc.checked_mul(base)); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return Some(acc); + } } exp /= 2; base = try_opt!(base.checked_mul(base)); } - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc.checked_mul(base) } /// Strict exponentiation. Computes `self.pow(exp)`, panicking if @@ -1546,18 +1545,17 @@ macro_rules! int_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc.strict_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base.strict_mul(base); } - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc.strict_mul(base) } /// Returns the square root of the number, rounded down. @@ -2181,19 +2179,17 @@ macro_rules! int_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc.wrapping_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base.wrapping_mul(base); } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc.wrapping_mul(base) } /// Calculates `self` + `rhs` @@ -2687,9 +2683,14 @@ macro_rules! int_impl { // Scratch space for storing results of overflowing_mul. let mut r; - while exp > 1 { + loop { if (exp & 1) == 1 { r = acc.overflowing_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + r.1 |= overflown; + return r; + } acc = r.0; overflown |= r.1; } @@ -2698,14 +2699,6 @@ macro_rules! int_impl { base = r.0; overflown |= r.1; } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - r = acc.overflowing_mul(base); - r.1 |= overflown; - r } /// Raises self to the power of `exp`, using exponentiation by squaring. @@ -2732,19 +2725,17 @@ macro_rules! int_impl { let mut base = self; let mut acc = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc * base; + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base * base; } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base } /// Returns the square root of the number, rounded down. diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index ad72c29758bd7..b272a9d901bf4 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1534,20 +1534,17 @@ macro_rules! uint_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = try_opt!(acc.checked_mul(base)); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return Some(acc); + } } exp /= 2; base = try_opt!(base.checked_mul(base)); } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - - acc.checked_mul(base) } /// Strict exponentiation. Computes `self.pow(exp)`, panicking if @@ -1587,18 +1584,17 @@ macro_rules! uint_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc.strict_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base.strict_mul(base); } - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc.strict_mul(base) } /// Saturating integer addition. Computes `self + rhs`, saturating at @@ -2059,19 +2055,17 @@ macro_rules! uint_impl { let mut base = self; let mut acc: Self = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc.wrapping_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base.wrapping_mul(base); } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc.wrapping_mul(base) } /// Calculates `self` + `rhs` @@ -2516,9 +2510,14 @@ macro_rules! uint_impl { // Scratch space for storing results of overflowing_mul. let mut r; - while exp > 1 { + loop { if (exp & 1) == 1 { r = acc.overflowing_mul(base); + // since exp!=0, finally the exp must be 1. + if exp == 1 { + r.1 |= overflown; + return r; + } acc = r.0; overflown |= r.1; } @@ -2527,15 +2526,6 @@ macro_rules! uint_impl { base = r.0; overflown |= r.1; } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - r = acc.overflowing_mul(base); - r.1 |= overflown; - - r } /// Raises self to the power of `exp`, using exponentiation by squaring. @@ -2560,19 +2550,17 @@ macro_rules! uint_impl { let mut base = self; let mut acc = 1; - while exp > 1 { + loop { if (exp & 1) == 1 { acc = acc * base; + // since exp!=0, finally the exp must be 1. + if exp == 1 { + return acc; + } } exp /= 2; base = base * base; } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base } /// Returns the square root of the number, rounded down. From 1faa1018c7ddd2f505904b0f71255afa5ae1bbe7 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 12 Jul 2024 00:54:26 +0300 Subject: [PATCH 004/685] Explicitly unroll integer pow for small exponents The newly optimized loop has introduced a regression in the case when pow is called with a small constant exponent. LLVM is no longer able to unroll the loop and the generated code is larger and slower than what's expected in tests. Match and handle small exponent values separately by branching out to an explicit multiplication sequence for that exponent. Powers larger than 6 need more than three multiplications, so these cases are less likely to benefit from this optimization, also such constant exponents are less likely to be used in practice. For uses with a non-constant exponent, this might also provide a performance benefit if the exponent is small and does not vary between successive calls, so the same match arm tends to be taken as a predicted branch. --- library/core/src/num/int_macros.rs | 62 ++++++++++++++++++++++++++--- library/core/src/num/uint_macros.rs | 62 ++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 6ed0eb07e48d8..d1bb5a6ef478a 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2173,10 +2173,35 @@ macro_rules! int_impl { without modifying the original"] #[inline] pub const fn wrapping_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } let mut base = self; + + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base.wrapping_mul(base), + 3 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(base); + } + 4 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared); + } + 5 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared).wrapping_mul(base); + } + 6 => { + let cubed = base.wrapping_mul(base).wrapping_mul(base); + return cubed.wrapping_mul(cubed); + } + _ => {} + } + let mut acc: Self = 1; loop { @@ -2719,10 +2744,35 @@ macro_rules! int_impl { #[inline] #[rustc_inherit_overflow_checks] pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } let mut base = self; + + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base * base, + 3 => { + let squared = base * base; + return squared * base; + } + 4 => { + let squared = base * base; + return squared * squared; + } + 5 => { + let squared = base * base; + return squared * squared * base; + } + 6 => { + let cubed = base * base * base; + return cubed * cubed; + } + _ => {} + } + let mut acc = 1; loop { diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index b272a9d901bf4..6e5d37f8163ea 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2049,10 +2049,35 @@ macro_rules! uint_impl { without modifying the original"] #[inline] pub const fn wrapping_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } let mut base = self; + + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base.wrapping_mul(base), + 3 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(base); + } + 4 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared); + } + 5 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared).wrapping_mul(base); + } + 6 => { + let cubed = base.wrapping_mul(base).wrapping_mul(base); + return cubed.wrapping_mul(cubed); + } + _ => {} + } + let mut acc: Self = 1; loop { @@ -2544,10 +2569,35 @@ macro_rules! uint_impl { #[inline] #[rustc_inherit_overflow_checks] pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } let mut base = self; + + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base * base, + 3 => { + let squared = base * base; + return squared * base; + } + 4 => { + let squared = base * base; + return squared * squared; + } + 5 => { + let squared = base * base; + return squared * squared * base; + } + 6 => { + let cubed = base * base * base; + return cubed * cubed; + } + _ => {} + } + let mut acc = 1; loop { From 2f235343529c39bdab47704ec9620d6784eeeb6d Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 12 Jul 2024 22:54:08 +0300 Subject: [PATCH 005/685] Use is_val_statically_known to optimize pow In the dynamic exponent case, it's preferred to not increase code size, so use solely the loop-based implementation there. This shows about 4% penalty in the variable exponent benchmarks on x86_64. --- library/core/src/lib.rs | 1 + library/core/src/num/int_macros.rs | 108 ++++++++++++++++------------ library/core/src/num/uint_macros.rs | 108 ++++++++++++++++------------ 3 files changed, 125 insertions(+), 92 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 0f82f01e57a71..ede95e3b2ca9b 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -170,6 +170,7 @@ #![feature(internal_impls_macro)] #![feature(ip)] #![feature(is_ascii_octdigit)] +#![feature(is_val_statically_known)] #![feature(isqrt)] #![feature(link_cfg)] #![feature(offset_of_enum)] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index d1bb5a6ef478a..be0e6a2a03b70 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2172,35 +2172,43 @@ macro_rules! int_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn wrapping_pow(self, mut exp: u32) -> Self { let mut base = self; - // Unroll multiplications for small exponent values. - // This gives the optimizer a way to efficiently inline call sites - // for the most common use cases with constant exponents. - // Currently, LLVM is unable to unroll the loop below. - match exp { - 0 => return 1, - 1 => return base, - 2 => return base.wrapping_mul(base), - 3 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(base); - } - 4 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(squared); - } - 5 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(squared).wrapping_mul(base); + if intrinsics::is_val_statically_known(exp) { + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base.wrapping_mul(base), + 3 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(base); + } + 4 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared); + } + 5 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared).wrapping_mul(base); + } + 6 => { + let cubed = base.wrapping_mul(base).wrapping_mul(base); + return cubed.wrapping_mul(cubed); + } + _ => {} } - 6 => { - let cubed = base.wrapping_mul(base).wrapping_mul(base); - return cubed.wrapping_mul(cubed); + } else { + if exp == 0 { + return 1; } - _ => {} } + debug_assert!(exp != 0); let mut acc: Self = 1; @@ -2743,35 +2751,43 @@ macro_rules! int_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn pow(self, mut exp: u32) -> Self { let mut base = self; - // Unroll multiplications for small exponent values. - // This gives the optimizer a way to efficiently inline call sites - // for the most common use cases with constant exponents. - // Currently, LLVM is unable to unroll the loop below. - match exp { - 0 => return 1, - 1 => return base, - 2 => return base * base, - 3 => { - let squared = base * base; - return squared * base; - } - 4 => { - let squared = base * base; - return squared * squared; - } - 5 => { - let squared = base * base; - return squared * squared * base; + if intrinsics::is_val_statically_known(exp) { + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base * base, + 3 => { + let squared = base * base; + return squared * base; + } + 4 => { + let squared = base * base; + return squared * squared; + } + 5 => { + let squared = base * base; + return squared * squared * base; + } + 6 => { + let cubed = base * base * base; + return cubed * cubed; + } + _ => {} } - 6 => { - let cubed = base * base * base; - return cubed * cubed; + } else { + if exp == 0 { + return 1; } - _ => {} } + debug_assert!(exp != 0); let mut acc = 1; diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 6e5d37f8163ea..24352593fca3d 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2048,35 +2048,43 @@ macro_rules! uint_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn wrapping_pow(self, mut exp: u32) -> Self { let mut base = self; - // Unroll multiplications for small exponent values. - // This gives the optimizer a way to efficiently inline call sites - // for the most common use cases with constant exponents. - // Currently, LLVM is unable to unroll the loop below. - match exp { - 0 => return 1, - 1 => return base, - 2 => return base.wrapping_mul(base), - 3 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(base); - } - 4 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(squared); - } - 5 => { - let squared = base.wrapping_mul(base); - return squared.wrapping_mul(squared).wrapping_mul(base); + if intrinsics::is_val_statically_known(exp) { + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base.wrapping_mul(base), + 3 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(base); + } + 4 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared); + } + 5 => { + let squared = base.wrapping_mul(base); + return squared.wrapping_mul(squared).wrapping_mul(base); + } + 6 => { + let cubed = base.wrapping_mul(base).wrapping_mul(base); + return cubed.wrapping_mul(cubed); + } + _ => {} } - 6 => { - let cubed = base.wrapping_mul(base).wrapping_mul(base); - return cubed.wrapping_mul(cubed); + } else { + if exp == 0 { + return 1; } - _ => {} } + debug_assert!(exp != 0); let mut acc: Self = 1; @@ -2568,35 +2576,43 @@ macro_rules! uint_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn pow(self, mut exp: u32) -> Self { let mut base = self; - // Unroll multiplications for small exponent values. - // This gives the optimizer a way to efficiently inline call sites - // for the most common use cases with constant exponents. - // Currently, LLVM is unable to unroll the loop below. - match exp { - 0 => return 1, - 1 => return base, - 2 => return base * base, - 3 => { - let squared = base * base; - return squared * base; - } - 4 => { - let squared = base * base; - return squared * squared; - } - 5 => { - let squared = base * base; - return squared * squared * base; + if intrinsics::is_val_statically_known(exp) { + // Unroll multiplications for small exponent values. + // This gives the optimizer a way to efficiently inline call sites + // for the most common use cases with constant exponents. + // Currently, LLVM is unable to unroll the loop below. + match exp { + 0 => return 1, + 1 => return base, + 2 => return base * base, + 3 => { + let squared = base * base; + return squared * base; + } + 4 => { + let squared = base * base; + return squared * squared; + } + 5 => { + let squared = base * base; + return squared * squared * base; + } + 6 => { + let cubed = base * base * base; + return cubed * cubed; + } + _ => {} } - 6 => { - let cubed = base * base * base; - return cubed * cubed; + } else { + if exp == 0 { + return 1; } - _ => {} } + debug_assert!(exp != 0); let mut acc = 1; From 89f3064e344dd1ea2046c05e070fbc3a4ee7f7f7 Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Tue, 9 Aug 2022 01:28:42 +0200 Subject: [PATCH 006/685] Add powerpc-unknown-linux-muslspe compile target This is almost identical to already existing targets: - powerpc_unknown_linux_musl.rs - powerpc_unknown_linux_gnuspe.rs It has support for PowerPC SPE (muslspe), which can be used with GCC version up to 8. It is useful for Freescale or IBM cores like e500. This was verified to be working with OpenWrt build system for CZ.NIC's Turris 1.x routers, which are using Freescale P2020, e500v2, so add it as a Tier 3 target. --- compiler/rustc_target/src/spec/mod.rs | 1 + .../targets/powerpc_unknown_linux_muslspe.rs | 28 ++++++++++++++++ src/doc/rustc/src/SUMMARY.md | 1 + src/doc/rustc/src/platform-support.md | 1 + .../powerpc-unknown-linux-muslspe.md | 32 +++++++++++++++++++ tests/assembly/targets/targets-elf.rs | 3 ++ 6 files changed, 66 insertions(+) create mode 100644 compiler/rustc_target/src/spec/targets/powerpc_unknown_linux_muslspe.rs create mode 100644 src/doc/rustc/src/platform-support/powerpc-unknown-linux-muslspe.md diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 607eeac7ccdc3..e5ffc94d083ce 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1558,6 +1558,7 @@ supported_targets! { ("powerpc-unknown-linux-gnu", powerpc_unknown_linux_gnu), ("powerpc-unknown-linux-gnuspe", powerpc_unknown_linux_gnuspe), ("powerpc-unknown-linux-musl", powerpc_unknown_linux_musl), + ("powerpc-unknown-linux-muslspe", powerpc_unknown_linux_muslspe), ("powerpc64-ibm-aix", powerpc64_ibm_aix), ("powerpc64-unknown-linux-gnu", powerpc64_unknown_linux_gnu), ("powerpc64-unknown-linux-musl", powerpc64_unknown_linux_musl), diff --git a/compiler/rustc_target/src/spec/targets/powerpc_unknown_linux_muslspe.rs b/compiler/rustc_target/src/spec/targets/powerpc_unknown_linux_muslspe.rs new file mode 100644 index 0000000000000..d19015729ec19 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/powerpc_unknown_linux_muslspe.rs @@ -0,0 +1,28 @@ +use crate::abi::Endian; +use crate::spec::{base, Cc, LinkerFlavor, Lld, StackProbeType, Target, TargetOptions}; + +pub fn target() -> Target { + let mut base = base::linux_musl::opts(); + base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-mspe"]); + base.max_atomic_width = Some(32); + base.stack_probes = StackProbeType::Inline; + + Target { + llvm_target: "powerpc-unknown-linux-muslspe".into(), + metadata: crate::spec::TargetMetadata { + description: Some("PowerPC SPE Linux with musl".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 32, + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), + arch: "powerpc".into(), + options: TargetOptions { + abi: "spe".into(), + endian: Endian::Big, + mcount: "_mcount".into(), + ..base + }, + } +} diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 1a8ff931f0177..e5883cb971491 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -61,6 +61,7 @@ - [mipsisa\*r6\*-unknown-linux-gnu\*](platform-support/mips-release-6.md) - [nvptx64-nvidia-cuda](platform-support/nvptx64-nvidia-cuda.md) - [powerpc-unknown-openbsd](platform-support/powerpc-unknown-openbsd.md) + - [powerpc-unknown-linux-muslspe](platform-support/powerpc-unknown-linux-muslspe.md) - [powerpc64-ibm-aix](platform-support/aix.md) - [riscv32im-risc0-zkvm-elf](platform-support/riscv32im-risc0-zkvm-elf.md) - [riscv32imac-unknown-xous-elf](platform-support/riscv32imac-unknown-xous-elf.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 370dbed50fa1a..57187bbe902a4 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -331,6 +331,7 @@ target | std | host | notes `msp430-none-elf` | * | | 16-bit MSP430 microcontrollers `powerpc-unknown-linux-gnuspe` | ✓ | | PowerPC SPE Linux `powerpc-unknown-linux-musl` | ? | | PowerPC Linux with musl 1.2.3 +[`powerpc-unknown-linux-muslspe`](platform-support/powerpc-unknown-linux-muslspe.md) | ? | | PowerPC SPE Linux [`powerpc-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD 32-bit powerpc systems [`powerpc-unknown-openbsd`](platform-support/powerpc-unknown-openbsd.md) | * | | `powerpc-wrs-vxworks-spe` | ? | | diff --git a/src/doc/rustc/src/platform-support/powerpc-unknown-linux-muslspe.md b/src/doc/rustc/src/platform-support/powerpc-unknown-linux-muslspe.md new file mode 100644 index 0000000000000..4c416b5192994 --- /dev/null +++ b/src/doc/rustc/src/platform-support/powerpc-unknown-linux-muslspe.md @@ -0,0 +1,32 @@ +# powerpc-unknown-linux-muslspe + +**Tier: 3** + +This target is very similar to already existing ones like `powerpc_unknown_linux_musl` and `powerpc_unknown_linux_gnuspe`. +This one has PowerPC SPE support for musl. Unfortunately, the last supported gcc version with PowerPC SPE is 8.4.0. + +## Target maintainers + +- [@BKPepe](https://github.com/BKPepe) + +## Requirements + +This target is cross-compiled. There is no support for `std`. There is no +default allocator, but it's possible to use `alloc` by supplying an allocator. + +This target generated binaries in the ELF format. + +## Building the target + +This target was tested and used within the `OpenWrt` build system for CZ.NIC Turris 1.x routers using Freescale P2020. + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will either need to build Rust with the target enabled (see +"Building the target" above), or build your own copy of `core` by using +`build-std` or similar. + +## Testing + +This is a cross-compiled target and there is no support to run rustc test suite. diff --git a/tests/assembly/targets/targets-elf.rs b/tests/assembly/targets/targets-elf.rs index 32cce3839dc26..59ee0d47bba08 100644 --- a/tests/assembly/targets/targets-elf.rs +++ b/tests/assembly/targets/targets-elf.rs @@ -345,6 +345,9 @@ //@ revisions: powerpc_unknown_linux_musl //@ [powerpc_unknown_linux_musl] compile-flags: --target powerpc-unknown-linux-musl //@ [powerpc_unknown_linux_musl] needs-llvm-components: powerpc +//@ revisions: powerpc_unknown_linux_muslspe +//@ [powerpc_unknown_linux_muslspe] compile-flags: --target powerpc-unknown-linux-muslspe +//@ [powerpc_unknown_linux_muslspe] needs-llvm-components: powerpc //@ revisions: powerpc_unknown_netbsd //@ [powerpc_unknown_netbsd] compile-flags: --target powerpc-unknown-netbsd //@ [powerpc_unknown_netbsd] needs-llvm-components: powerpc From 68fb25e2eb4898551037a62b753bc9702f868fe3 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 18 Jul 2024 20:32:08 -0400 Subject: [PATCH 007/685] Make use of raw strings in `core::fmt::builders` There are quite a few uses of escaped quotes. Turn these into raw strings within documentation and tests to make things easier to read. --- library/core/src/fmt/builders.rs | 24 +++++----- library/core/tests/fmt/builders.rs | 74 +++++++++++++++--------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 4ccb585862cdf..944f7c0850706 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -78,7 +78,7 @@ impl fmt::Write for PadAdapter<'_, '_> { /// /// assert_eq!( /// format!("{:?}", Foo { bar: 10, baz: "Hello World".to_string() }), -/// "Foo { bar: 10, baz: \"Hello World\" }", +/// r#"Foo { bar: 10, baz: "Hello World" }"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -125,7 +125,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Bar { bar: 10, another: "Hello World".to_string() }), - /// "Bar { bar: 10, another: \"Hello World\", nonexistent_field: 1 }", + /// r#"Bar { bar: 10, another: "Hello World", nonexistent_field: 1 }"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -237,7 +237,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Bar { bar: 10, baz: "Hello World".to_string() }), - /// "Bar { bar: 10, baz: \"Hello World\" }", + /// r#"Bar { bar: 10, baz: "Hello World" }"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -280,7 +280,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), -/// "Foo(10, \"Hello World\")", +/// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -322,7 +322,7 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), - /// "Foo(10, \"Hello World\")", + /// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -381,7 +381,7 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), - /// "Foo(10, \"Hello World\")", + /// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -747,7 +747,7 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), -/// "{\"A\": 10, \"B\": 11}", +/// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -787,7 +787,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -823,7 +823,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_map_key_value", since = "1.42.0")] @@ -899,7 +899,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_map_key_value", since = "1.42.0")] @@ -957,7 +957,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"A\": 10, \"B\": 11}", + /// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -997,7 +997,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"A\": 10, \"B\": 11}", + /// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] diff --git a/library/core/tests/fmt/builders.rs b/library/core/tests/fmt/builders.rs index 2bdc334b7c027..7b73f13815107 100644 --- a/library/core/tests/fmt/builders.rs +++ b/library/core/tests/fmt/builders.rs @@ -79,23 +79,23 @@ mod debug_struct { } assert_eq!( - "Bar { foo: Foo { bar: true, baz: 10/20 }, hello: \"world\" }", + r#"Bar { foo: Foo { bar: true, baz: 10/20 }, hello: "world" }"#, format!("{Bar:?}") ); assert_eq!( - "Bar { + r#"Bar { foo: Foo { bar: true, baz: 10/20, }, - hello: \"world\", -}", + hello: "world", +}"#, format!("{Bar:#?}") ); } #[test] - fn test_only_non_exhaustive() { + fn test_empty_non_exhaustive() { struct Foo; impl fmt::Debug for Foo { @@ -157,19 +157,19 @@ mod debug_struct { } assert_eq!( - "Bar { foo: Foo { bar: true, baz: 10/20, .. }, hello: \"world\", .. }", + r#"Bar { foo: Foo { bar: true, baz: 10/20, .. }, hello: "world", .. }"#, format!("{Bar:?}") ); assert_eq!( - "Bar { + r#"Bar { foo: Foo { bar: true, baz: 10/20, .. }, - hello: \"world\", + hello: "world", .. -}", +}"#, format!("{Bar:#?}") ); } @@ -249,15 +249,15 @@ mod debug_tuple { } } - assert_eq!("Bar(Foo(true, 10/20), \"world\")", format!("{Bar:?}")); + assert_eq!(r#"Bar(Foo(true, 10/20), "world")"#, format!("{Bar:?}")); assert_eq!( - "Bar( + r#"Bar( Foo( true, 10/20, ), - \"world\", -)", + "world", +)"#, format!("{Bar:#?}") ); } @@ -301,11 +301,11 @@ mod debug_map { assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); - assert_eq!("{\"bar\": true}", format!("{Entry:?}")); + assert_eq!(r#"{"bar": true}"#, format!("{Entry:?}")); assert_eq!( - "{ - \"bar\": true, -}", + r#"{ + "bar": true, +}"#, format!("{Entry:#?}") ); } @@ -339,12 +339,12 @@ mod debug_map { assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); - assert_eq!("{\"bar\": true, 10: 10/20}", format!("{Entry:?}")); + assert_eq!(r#"{"bar": true, 10: 10/20}"#, format!("{Entry:?}")); assert_eq!( - "{ - \"bar\": true, + r#"{ + "bar": true, 10: 10/20, -}", +}"#, format!("{Entry:#?}") ); } @@ -371,21 +371,21 @@ mod debug_map { } assert_eq!( - "{\"foo\": {\"bar\": true, 10: 10/20}, \ - {\"bar\": true, 10: 10/20}: \"world\"}", + r#"{"foo": {"bar": true, 10: 10/20}, \ + {"bar": true, 10: 10/20}: "world"}"#, format!("{Bar:?}") ); assert_eq!( - "{ - \"foo\": { - \"bar\": true, + r#"{ + "foo": { + "bar": true, 10: 10/20, }, { - \"bar\": true, + "bar": true, 10: 10/20, - }: \"world\", -}", + }: "world", +}"#, format!("{Bar:#?}") ); } @@ -547,15 +547,15 @@ mod debug_set { } } - assert_eq!("{{true, 10/20}, \"world\"}", format!("{Bar:?}")); + assert_eq!(r#"{{true, 10/20}, "world"}"#, format!("{Bar:?}")); assert_eq!( - "{ + r#"{ { true, 10/20, }, - \"world\", -}", + "world", +}"#, format!("{Bar:#?}") ); } @@ -635,15 +635,15 @@ mod debug_list { } } - assert_eq!("[[true, 10/20], \"world\"]", format!("{Bar:?}")); + assert_eq!(r#"[[true, 10/20], "world"]"#, format!("{Bar:?}")); assert_eq!( - "[ + r#"[ [ true, 10/20, ], - \"world\", -]", + "world", +]"#, format!("{Bar:#?}") ); } From 827970ebe9fcce27ada9f71977895666f56032be Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 18 Jul 2024 20:33:22 -0400 Subject: [PATCH 008/685] Implement `debug_more_non_exhaustive` Add a `.finish_non_exhaustive()` method to `DebugTuple`, `DebugSet`, `DebugList`, and `DebugMap`. This indicates that the structures have remaining items with `..`. This implements the ACP at . --- library/core/src/fmt/builders.rs | 200 ++++++++++++++++++ library/core/tests/fmt/builders.rs | 322 ++++++++++++++++++++++++++++- library/core/tests/lib.rs | 1 + 3 files changed, 521 insertions(+), 2 deletions(-) diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 944f7c0850706..1d04320831df3 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -360,6 +360,51 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { self } + /// Marks the tuple struct as non-exhaustive, indicating to the reader that there are some + /// other fields that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(i32, String); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt.debug_tuple("Foo") + /// .field(&self.0) + /// .finish_non_exhaustive() // Show that some other field(s) exist. + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(10, "secret!".to_owned())), + /// "Foo(10, ..)", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.result = self.result.and_then(|_| { + if self.fields > 0 { + if self.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.fmt.write_str(")") + } else { + self.fmt.write_str(", ..)") + } + } else { + self.fmt.write_str("(..)") + } + }); + self.result + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -554,6 +599,56 @@ impl<'a, 'b: 'a> DebugSet<'a, 'b> { self } + /// Marks the set as non-exhaustive, indicating to the reader that there are some other + /// elements that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_set(); + /// let mut f = f.entries(self.0.iter().take(2)); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![1, 2, 3, 4])), + /// "{1, 2, ..}", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.inner.result = self.inner.result.and_then(|_| { + if self.inner.has_fields { + if self.inner.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.inner.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.inner.fmt.write_str("}") + } else { + self.inner.fmt.write_str(", ..}") + } + } else { + self.inner.fmt.write_str("..}") + } + }); + self.inner.result + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -697,6 +792,55 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> { self } + /// Marks the list as non-exhaustive, indicating to the reader that there are some other + /// elements that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_list(); + /// let mut f = f.entries(self.0.iter().take(2)); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![1, 2, 3, 4])), + /// "[1, 2, ..]", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.inner.result.and_then(|_| { + if self.inner.has_fields { + if self.inner.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.inner.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.inner.fmt.write_str("]") + } else { + self.inner.fmt.write_str(", ..]") + } + } else { + self.inner.fmt.write_str("..]") + } + }) + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -973,6 +1117,62 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { self } + /// Marks the map as non-exhaustive, indicating to the reader that there are some other + /// entries that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec<(String, i32)>); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_map(); + /// let mut f = f.entries(self.0.iter().take(2).map(|&(ref k, ref v)| (k, v))); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![ + /// ("A".to_string(), 10), + /// ("B".to_string(), 11), + /// ("C".to_string(), 12), + /// ])), + /// r#"{"A": 10, "B": 11, ..}"#, + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.result = self.result.and_then(|_| { + assert!(!self.has_key, "attempted to finish a map with a partial entry"); + + if self.has_fields { + if self.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.fmt.write_str("}") + } else { + self.fmt.write_str(", ..}") + } + } else { + self.fmt.write_str("..}") + } + }); + self.result + } + /// Finishes output and returns any error encountered. /// /// # Panics diff --git a/library/core/tests/fmt/builders.rs b/library/core/tests/fmt/builders.rs index 7b73f13815107..ba4801f5912b8 100644 --- a/library/core/tests/fmt/builders.rs +++ b/library/core/tests/fmt/builders.rs @@ -257,6 +257,80 @@ mod debug_tuple { 10/20, ), "world", +)"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo").finish_non_exhaustive() + } + } + + assert_eq!("Foo(..)", format!("{Foo:?}")); + assert_eq!("Foo(..)", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo") + .field(&true) + .field(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("Foo(true, 10/20, ..)", format!("{Foo:?}")); + assert_eq!( + "Foo( + true, + 10/20, + .. +)", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo") + .field(&true) + .field(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Bar").field(&Foo).field(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"Bar(Foo(true, 10/20, ..), "world", ..)"#, format!("{Bar:?}")); + assert_eq!( + r#"Bar( + Foo( + true, + 10/20, + .. + ), + "world", + .. )"#, format!("{Bar:#?}") ); @@ -371,8 +445,7 @@ mod debug_map { } assert_eq!( - r#"{"foo": {"bar": true, 10: 10/20}, \ - {"bar": true, 10: 10/20}: "world"}"#, + r#"{"foo": {"bar": true, 10: 10/20}, {"bar": true, 10: 10/20}: "world"}"#, format!("{Bar:?}") ); assert_eq!( @@ -471,6 +544,103 @@ mod debug_map { let _ = format!("{Foo:?}"); } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map().finish_non_exhaustive() + } + } + + assert_eq!("{..}", format!("{Foo:?}")); + assert_eq!("{..}", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Entry; + + impl fmt::Debug for Entry { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .entry(&"bar", &true) + .entry(&10, &format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct KeyValue; + + impl fmt::Debug for KeyValue { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar") + .value(&true) + .key(&10) + .value(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); + assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); + + assert_eq!(r#"{"bar": true, 10: 10/20, ..}"#, format!("{Entry:?}")); + assert_eq!( + r#"{ + "bar": true, + 10: 10/20, + .. +}"#, + format!("{Entry:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .entry(&"bar", &true) + .entry(&10, &format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map().entry(&"foo", &Foo).entry(&Foo, &"world").finish_non_exhaustive() + } + } + + assert_eq!( + r#"{"foo": {"bar": true, 10: 10/20, ..}, {"bar": true, 10: 10/20, ..}: "world", ..}"#, + format!("{Bar:?}") + ); + assert_eq!( + r#"{ + "foo": { + "bar": true, + 10: 10/20, + .. + }, + { + "bar": true, + 10: 10/20, + .. + }: "world", + .. +}"#, + format!("{Bar:#?}") + ); + } } mod debug_set { @@ -555,6 +725,80 @@ mod debug_set { 10/20, }, "world", +}"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set().finish_non_exhaustive() + } + } + + assert_eq!("{..}", format!("{Foo:?}")); + assert_eq!("{..}", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("{true, 10/20, ..}", format!("{Foo:?}")); + assert_eq!( + "{ + true, + 10/20, + .. +}", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set().entry(&Foo).entry(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"{{true, 10/20, ..}, "world", ..}"#, format!("{Bar:?}")); + assert_eq!( + r#"{ + { + true, + 10/20, + .. + }, + "world", + .. }"#, format!("{Bar:#?}") ); @@ -643,6 +887,80 @@ mod debug_list { 10/20, ], "world", +]"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list().finish_non_exhaustive() + } + } + + assert_eq!("[..]", format!("{Foo:?}")); + assert_eq!("[..]", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("[true, 10/20, ..]", format!("{Foo:?}")); + assert_eq!( + "[ + true, + 10/20, + .. +]", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list().entry(&Foo).entry(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"[[true, 10/20, ..], "world", ..]"#, format!("{Bar:?}")); + assert_eq!( + r#"[ + [ + true, + 10/20, + .. + ], + "world", + .. ]"#, format!("{Bar:#?}") ); diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 83a615fcd8be3..0f6ba7e4c66cb 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -29,6 +29,7 @@ #![feature(core_io_borrowed_buf)] #![feature(core_private_bignum)] #![feature(core_private_diy_float)] +#![feature(debug_more_non_exhaustive)] #![feature(dec2flt)] #![feature(duration_consts_float)] #![feature(duration_constants)] From b18c7d85a92011bcc094b33e6a3c646a3cfca8f3 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Mon, 22 Jul 2024 17:34:29 +0100 Subject: [PATCH 009/685] Docs for Waker and LocalWaker: Add cross-refs in comment --- library/core/src/task/wake.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/core/src/task/wake.rs b/library/core/src/task/wake.rs index 86a965f68e085..4582bf9f42245 100644 --- a/library/core/src/task/wake.rs +++ b/library/core/src/task/wake.rs @@ -531,6 +531,10 @@ impl Waker { /// Returns a reference to a `Waker` that does nothing when used. /// + // Note! Much of the documentation for this method is duplicated + // in the docs for `LocalWaker::noop`. + // If you edit it, consider editing the other copy too. + // /// This is mostly useful for writing tests that need a [`Context`] to poll /// some futures, but are not expecting those futures to wake the waker or /// do not need to do anything specific if it happens. @@ -784,6 +788,10 @@ impl LocalWaker { /// Creates a new `LocalWaker` that does nothing when `wake` is called. /// + // Note! Much of the documentation for this method is duplicated + // in the docs for `Waker::noop`. + // If you edit it, consider editing the other copy too. + // /// This is mostly useful for writing tests that need a [`Context`] to poll /// some futures, but are not expecting those futures to wake the waker or /// do not need to do anything specific if it happens. From c404406a87412d50604893d8c347744195ce2dea Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Mon, 22 Jul 2024 17:35:11 +0100 Subject: [PATCH 010/685] LocalWaker docs: Make long-ago omitted but probably intended changes In 6f8a944ba4311cbcf5922132721095c226c6fbab, titled Change return type of unstable `Waker::noop()` from `Waker` to `&Waker`. the summary line for Waker was changed: - /// Creates a new `Waker` that does nothing when `wake` is called. + /// Returns a reference to a `Waker` that does nothing when used. and the sentence about clone was added. LocalWaker's docs were not changed, even though the types were, but there is no explanation for why not. It seems like it was simply a slip induced by the clone-and-hack. --- library/core/src/task/wake.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/core/src/task/wake.rs b/library/core/src/task/wake.rs index 4582bf9f42245..baa8a956f8af1 100644 --- a/library/core/src/task/wake.rs +++ b/library/core/src/task/wake.rs @@ -786,7 +786,7 @@ impl LocalWaker { Self { waker } } - /// Creates a new `LocalWaker` that does nothing when `wake` is called. + /// Returns a reference to a `LocalWaker` that does nothing when used. /// // Note! Much of the documentation for this method is duplicated // in the docs for `Waker::noop`. @@ -796,6 +796,8 @@ impl LocalWaker { /// some futures, but are not expecting those futures to wake the waker or /// do not need to do anything specific if it happens. /// + /// If an owned `LocalWaker` is needed, `clone()` this one. + /// /// # Examples /// /// ``` From b4b991e66f0a1fd0e517ed7f038129350a7ad6ce Mon Sep 17 00:00:00 2001 From: surechen Date: Tue, 23 Jul 2024 10:24:45 +0800 Subject: [PATCH 011/685] Suggest adding Result return type for associated method in E0277. For following: ```rust struct A; impl A { fn test4(&self) { let mut _file = File::create("foo.txt")?; //~^ ERROR the `?` operator can only be used in a method } ``` Suggest: ```rust impl A { fn test4(&self) -> Result<(), Box> { let mut _file = File::create("foo.txt")?; //~^ ERROR the `?` operator can only be used in a method Ok(()) } } ``` For #125997 --- .../src/error_reporting/traits/suggestions.rs | 41 +++++++++++++++--- ...turn-from-residual-sugg-issue-125997.fixed | 19 ++++++++ .../return-from-residual-sugg-issue-125997.rs | 15 +++++++ ...urn-from-residual-sugg-issue-125997.stderr | 43 ++++++++++++++++++- 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 885216e62165e..9b949323a360a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4612,6 +4612,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }) } + // For E0277 when use `?` operator, suggest adding + // a suitable return type in `FnSig`, and a default + // return value at the end of the function's body. pub(super) fn suggest_add_result_as_return_type( &self, obligation: &PredicateObligation<'tcx>, @@ -4622,19 +4625,47 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return; } + // Only suggest for local function and associated method, + // because this suggest adding both return type in + // the `FnSig` and a default return value in the body, so it + // is not suitable for foreign function without a local body, + // and neighter for trait method which may be also implemented + // in other place, so shouldn't change it's FnSig. + fn choose_suggest_items<'tcx, 'hir>( + tcx: TyCtxt<'tcx>, + node: hir::Node<'hir>, + ) -> Option<(&'hir hir::FnDecl<'hir>, hir::BodyId)> { + match node { + hir::Node::Item(item) if let hir::ItemKind::Fn(sig, _, body_id) = item.kind => { + Some((sig.decl, body_id)) + } + hir::Node::ImplItem(item) + if let hir::ImplItemKind::Fn(sig, body_id) = item.kind => + { + let parent = tcx.parent_hir_node(item.hir_id()); + if let hir::Node::Item(item) = parent + && let hir::ItemKind::Impl(imp) = item.kind + && imp.of_trait.is_none() + { + return Some((sig.decl, body_id)); + } + None + } + _ => None, + } + } + let node = self.tcx.hir_node_by_def_id(obligation.cause.body_id); - if let hir::Node::Item(item) = node - && let hir::ItemKind::Fn(sig, _, body_id) = item.kind - && let hir::FnRetTy::DefaultReturn(ret_span) = sig.decl.output + if let Some((fn_decl, body_id)) = choose_suggest_items(self.tcx, node) + && let hir::FnRetTy::DefaultReturn(ret_span) = fn_decl.output && self.tcx.is_diagnostic_item(sym::FromResidual, trait_pred.def_id()) && trait_pred.skip_binder().trait_ref.args.type_at(0).is_unit() && let ty::Adt(def, _) = trait_pred.skip_binder().trait_ref.args.type_at(1).kind() && self.tcx.is_diagnostic_item(sym::Result, def.did()) { - let body = self.tcx.hir().body(body_id); let mut sugg_spans = vec![(ret_span, " -> Result<(), Box>".to_string())]; - + let body = self.tcx.hir().body(body_id); if let hir::ExprKind::Block(b, _) = body.value.kind && b.expr.is_none() { diff --git a/tests/ui/return/return-from-residual-sugg-issue-125997.fixed b/tests/ui/return/return-from-residual-sugg-issue-125997.fixed index b2eca69aeb902..a5a133998259b 100644 --- a/tests/ui/return/return-from-residual-sugg-issue-125997.fixed +++ b/tests/ui/return/return-from-residual-sugg-issue-125997.fixed @@ -33,6 +33,25 @@ macro_rules! mac { }; } +struct A; + +impl A { + fn test4(&self) -> Result<(), Box> { + let mut _file = File::create("foo.txt")?; + //~^ ERROR the `?` operator can only be used in a method + + Ok(()) +} + + fn test5(&self) -> Result<(), Box> { + let mut _file = File::create("foo.txt")?; + //~^ ERROR the `?` operator can only be used in a method + println!(); + + Ok(()) +} +} + fn main() -> Result<(), Box> { let mut _file = File::create("foo.txt")?; //~^ ERROR the `?` operator can only be used in a function diff --git a/tests/ui/return/return-from-residual-sugg-issue-125997.rs b/tests/ui/return/return-from-residual-sugg-issue-125997.rs index dd8550a388b86..30ca27eae45ef 100644 --- a/tests/ui/return/return-from-residual-sugg-issue-125997.rs +++ b/tests/ui/return/return-from-residual-sugg-issue-125997.rs @@ -27,6 +27,21 @@ macro_rules! mac { }; } +struct A; + +impl A { + fn test4(&self) { + let mut _file = File::create("foo.txt")?; + //~^ ERROR the `?` operator can only be used in a method + } + + fn test5(&self) { + let mut _file = File::create("foo.txt")?; + //~^ ERROR the `?` operator can only be used in a method + println!(); + } +} + fn main() { let mut _file = File::create("foo.txt")?; //~^ ERROR the `?` operator can only be used in a function diff --git a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr index ef938f0213dfb..a59f38c2ec644 100644 --- a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr +++ b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr @@ -37,8 +37,47 @@ LL + Ok(()) LL + } | +error[E0277]: the `?` operator can only be used in a method that returns `Result` or `Option` (or another type that implements `FromResidual`) + --> $DIR/return-from-residual-sugg-issue-125997.rs:34:48 + | +LL | fn test4(&self) { + | --------------- this function should return `Result` or `Option` to accept `?` +LL | let mut _file = File::create("foo.txt")?; + | ^ cannot use the `?` operator in a method that returns `()` + | + = help: the trait `FromResidual>` is not implemented for `()` +help: consider adding return type + | +LL ~ fn test4(&self) -> Result<(), Box> { +LL | let mut _file = File::create("foo.txt")?; +LL | +LL ~ +LL + Ok(()) +LL + } + | + +error[E0277]: the `?` operator can only be used in a method that returns `Result` or `Option` (or another type that implements `FromResidual`) + --> $DIR/return-from-residual-sugg-issue-125997.rs:39:48 + | +LL | fn test5(&self) { + | --------------- this function should return `Result` or `Option` to accept `?` +LL | let mut _file = File::create("foo.txt")?; + | ^ cannot use the `?` operator in a method that returns `()` + | + = help: the trait `FromResidual>` is not implemented for `()` +help: consider adding return type + | +LL ~ fn test5(&self) -> Result<(), Box> { +LL | let mut _file = File::create("foo.txt")?; +LL | +LL | println!(); +LL ~ +LL + Ok(()) +LL + } + | + error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) - --> $DIR/return-from-residual-sugg-issue-125997.rs:31:44 + --> $DIR/return-from-residual-sugg-issue-125997.rs:46:44 | LL | fn main() { | --------- this function should return `Result` or `Option` to accept `?` @@ -81,6 +120,6 @@ LL + Ok(()) LL + } | -error: aborting due to 4 previous errors +error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0277`. From 2c166f4f55f4b3062f907e8a72cff56a98978e58 Mon Sep 17 00:00:00 2001 From: Neven Villani Date: Thu, 18 Jul 2024 13:32:24 +0200 Subject: [PATCH 012/685] Make unused states of Reserved unrepresentable --- .../src/borrow_tracker/tree_borrows/mod.rs | 25 ++- .../src/borrow_tracker/tree_borrows/perms.rs | 182 +++++++++++------- .../borrow_tracker/tree_borrows/tree/tests.rs | 35 +++- .../reserved/cell-protected-write.stderr | 10 +- .../reserved/int-protected-write.stderr | 10 +- .../tree_borrows/cell-alternate-writes.stderr | 2 +- .../pass/tree_borrows/end-of-protector.stderr | 22 +-- .../tests/pass/tree_borrows/formatting.stderr | 2 +- .../pass/tree_borrows/reborrow-is-read.stderr | 2 +- .../tests/pass/tree_borrows/reserved.stderr | 34 ++-- .../pass/tree_borrows/unique.default.stderr | 4 +- .../pass/tree_borrows/unique.uniq.stderr | 4 +- .../tree_borrows/vec_unique.default.stderr | 2 +- 13 files changed, 200 insertions(+), 134 deletions(-) diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 123d4b407fb4c..7871dbe16b340 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -1,10 +1,6 @@ use rustc_middle::{ mir::{Mutability, RetagKind}, - ty::{ - self, - layout::{HasParamEnv, HasTyCtxt}, - Ty, - }, + ty::{self, layout::HasParamEnv, Ty}, }; use rustc_span::def_id::DefId; use rustc_target::abi::{Abi, Size}; @@ -146,10 +142,9 @@ impl<'tcx> NewPermission { // interior mutability and protectors interact poorly. // To eliminate the case of Protected Reserved IM we override interior mutability // in the case of a protected reference: protected references are always considered - // "freeze". + // "freeze" in their reservation phase. let initial_state = match mutability { - Mutability::Mut if ty_is_unpin => - Permission::new_reserved(ty_is_freeze || is_protected), + Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze, is_protected), Mutability::Not if ty_is_freeze => Permission::new_frozen(), // Raw pointers never enter this function so they are not handled. // However raw pointers are not the only pointers that take the parent @@ -176,10 +171,12 @@ impl<'tcx> NewPermission { // Regular `Unpin` box, give it `noalias` but only a weak protector // because it is valid to deallocate it within the function. let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env()); + let protected = kind == RetagKind::FnEntry; + let initial_state = Permission::new_reserved(ty_is_freeze, protected); Self { zero_size, - initial_state: Permission::new_reserved(ty_is_freeze), - protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector), + initial_state, + protector: protected.then_some(ProtectorKind::WeakProtector), } }) } @@ -521,11 +518,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> { let this = self.eval_context_mut(); + // Note: if we were to inline `new_reserved` below we would find out that + // `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`. + // We are nevertheless including it here for clarity. + let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.param_env()); // Retag it. With protection! That is the entire point. let new_perm = NewPermission { - initial_state: Permission::new_reserved( - place.layout.ty.is_freeze(this.tcx(), this.param_env()), - ), + initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true), zero_size: false, protector: Some(ProtectorKind::StrongProtector), }; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs index 8e23257b6c006..5461edb51d3af 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs @@ -8,10 +8,16 @@ use crate::AccessKind; /// The activation states of a pointer. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum PermissionPriv { - /// represents: a local reference that has not yet been written to; - /// allows: child reads, foreign reads, foreign writes if type is freeze; + /// represents: a local mutable reference that has not yet been written to; + /// allows: child reads, foreign reads; /// affected by: child writes (becomes Active), - /// rejects: foreign writes (Disabled, except if type is not freeze). + /// rejects: foreign writes (Disabled). + /// + /// `ReservedFrz` is mostly for types that are `Freeze` (no interior mutability). + /// If the type has interior mutability, see `ReservedIM` instead. + /// (Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`, + /// we also use `ReservedFreeze` for mutable references that were retagged with a protector + /// independently of interior mutability) /// /// special case: behaves differently when protected, which is where `conflicted` /// is relevant @@ -22,12 +28,12 @@ enum PermissionPriv { /// - foreign-read then child-write is UB due to `conflicted`, /// - child-write then foreign-read is UB since child-write will activate and then /// foreign-read disables a protected `Active`, which is UB. - /// - /// Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`, - /// `ty_is_freeze` does not strictly mean that the type has no interior mutability, - /// it could be an interior mutable type that lost its interior mutability privileges - /// when retagged with a protector. - Reserved { ty_is_freeze: bool, conflicted: bool }, + ReservedFrz { conflicted: bool }, + /// Alternative version of `ReservedFrz` made for types with interior mutability. + /// allows: child reads, foreign reads, foreign writes (extra); + /// affected by: child writes (becomes Active); + /// rejects: nothing. + ReservedIM, /// represents: a unique pointer; /// allows: child reads, child writes; /// rejects: foreign reads (Frozen), foreign writes (Disabled). @@ -59,17 +65,14 @@ impl PartialOrd for PermissionPriv { (_, Frozen) => Less, (Active, _) => Greater, (_, Active) => Less, - ( - Reserved { ty_is_freeze: f1, conflicted: c1 }, - Reserved { ty_is_freeze: f2, conflicted: c2 }, - ) => { - // No transition ever changes `ty_is_freeze`. - if f1 != f2 { - return None; - } + (ReservedIM, ReservedIM) => Equal, + (ReservedFrz { conflicted: c1 }, ReservedFrz { conflicted: c2 }) => { // `bool` is ordered such that `false <= true`, so this works as intended. c1.cmp(c2) } + // Versions of `Reserved` with different interior mutability are incomparable with each + // other. + (ReservedIM, ReservedFrz { .. }) | (ReservedFrz { .. }, ReservedIM) => return None, }) } } @@ -77,7 +80,12 @@ impl PartialOrd for PermissionPriv { impl PermissionPriv { /// Check if `self` can be the initial state of a pointer. fn is_initial(&self) -> bool { - matches!(self, Reserved { conflicted: false, .. } | Frozen) + matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM) + } + + /// Reject `ReservedIM` that cannot exist in the presence of a protector. + fn compatible_with_protector(&self) -> bool { + !matches!(self, ReservedIM) } } @@ -93,7 +101,7 @@ mod transition { Disabled => return None, // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read // accesses, since the data is not being mutated. Hence the `{ .. }`. - readable @ (Reserved { .. } | Active | Frozen) => readable, + readable @ (ReservedFrz { .. } | ReservedIM | Active | Frozen) => readable, }) } @@ -109,11 +117,16 @@ mod transition { // Someone else read. To make sure we won't write before function exit, // we set the "conflicted" flag, which will disallow writes while we are protected. - Reserved { ty_is_freeze, .. } if protected => - Reserved { ty_is_freeze, conflicted: true }, + ReservedFrz { .. } if protected => ReservedFrz { conflicted: true }, // Before activation and without protectors, foreign reads are fine. // That's the entire point of 2-phase borrows. - res @ Reserved { .. } => res, + res @ (ReservedFrz { .. } | ReservedIM) => { + // Even though we haven't checked `ReservedIM if protected` separately, + // it is a state that cannot occur because under a protector we only + // create `ReservedFrz` never `ReservedIM`. + assert!(!protected); + res + } Active => if protected { // We wrote, someone else reads -- that's bad. @@ -134,10 +147,10 @@ mod transition { // If the `conflicted` flag is set, then there was a foreign read during // the function call that is still ongoing (still `protected`), // this is UB (`noalias` violation). - Reserved { conflicted: true, .. } if protected => return None, + ReservedFrz { conflicted: true } if protected => return None, // A write always activates the 2-phase borrow, even with interior // mutability - Reserved { .. } | Active => Active, + ReservedFrz { .. } | ReservedIM | Active => Active, Frozen | Disabled => return None, }) } @@ -145,15 +158,15 @@ mod transition { /// A non-child node was write-accessed: this makes everything `Disabled` except for /// non-protected interior mutable `Reserved` which stay the same. fn foreign_write(state: PermissionPriv, protected: bool) -> Option { + // There is no explicit dependency on `protected`, but recall that interior mutable + // types receive a `ReservedFrz` instead of `ReservedIM` when retagged under a protector, + // so the result of this function does indirectly depend on (past) protector status. Some(match state { - // FIXME: since the fix related to reservedim_spurious_write, it is now possible - // to express these transitions of the state machine without an explicit dependency - // on `protected`: because `ty_is_freeze: false` implies `!protected` then - // the line handling `Reserved { .. } if protected` could be deleted. - // This will however require optimizations to the exhaustive tests because - // fewer initial conditions are valid. - Reserved { .. } if protected => Disabled, - res @ Reserved { ty_is_freeze: false, .. } => res, + res @ ReservedIM => { + // We can never create a `ReservedIM` under a protector, only `ReservedFrz`. + assert!(!protected); + res + } _ => Disabled, }) } @@ -208,9 +221,23 @@ impl Permission { Self { inner: Active } } - /// Default initial permission of a reborrowed mutable reference. - pub fn new_reserved(ty_is_freeze: bool) -> Self { - Self { inner: Reserved { ty_is_freeze, conflicted: false } } + /// Default initial permission of a reborrowed mutable reference that is either + /// protected or not interior mutable. + fn new_reserved_frz() -> Self { + Self { inner: ReservedFrz { conflicted: false } } + } + + /// Default initial permission of an unprotected interior mutable reference. + fn new_reserved_im() -> Self { + Self { inner: ReservedIM } + } + + /// Wrapper around `new_reserved_frz` and `new_reserved_im` that decides + /// which to call based on the interior mutability and the retag kind (whether there + /// is a protector is relevant because being protected takes priority over being + /// interior mutable) + pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self { + if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() } } /// Default initial permission of a reborrowed shared reference. @@ -224,6 +251,11 @@ impl Permission { Self { inner: Disabled } } + /// Reject `ReservedIM` that cannot exist in the presence of a protector. + pub fn compatible_with_protector(&self) -> bool { + self.inner.compatible_with_protector() + } + /// Apply the transition to the inner PermissionPriv. pub fn perform_access( kind: AccessKind, @@ -279,12 +311,9 @@ pub mod diagnostics { f, "{}", match self { - Reserved { ty_is_freeze: true, conflicted: false } => "Reserved", - Reserved { ty_is_freeze: true, conflicted: true } => "Reserved (conflicted)", - Reserved { ty_is_freeze: false, conflicted: false } => - "Reserved (interior mutable)", - Reserved { ty_is_freeze: false, conflicted: true } => - "Reserved (interior mutable, conflicted)", + ReservedFrz { conflicted: false } => "Reserved", + ReservedFrz { conflicted: true } => "Reserved (conflicted)", + ReservedIM => "Reserved (interior mutable)", Active => "Active", Frozen => "Frozen", Disabled => "Disabled", @@ -312,10 +341,9 @@ pub mod diagnostics { // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise // alignment will be incorrect. match self.inner { - Reserved { ty_is_freeze: true, conflicted: false } => "Rs ", - Reserved { ty_is_freeze: true, conflicted: true } => "RsC ", - Reserved { ty_is_freeze: false, conflicted: false } => "RsM ", - Reserved { ty_is_freeze: false, conflicted: true } => "RsCM", + ReservedFrz { conflicted: false } => "Res ", + ReservedFrz { conflicted: true } => "ResC", + ReservedIM => "ReIM", Active => "Act ", Frozen => "Frz ", Disabled => "Dis ", @@ -325,13 +353,14 @@ pub mod diagnostics { impl PermTransition { /// Readable explanation of the consequences of an event. - /// Fits in the sentence "This accessed caused {trans.summary()}". + /// Fits in the sentence "This transition corresponds to {trans.summary()}". pub fn summary(&self) -> &'static str { assert!(self.is_possible()); + assert!(!self.is_noop()); match (self.from, self.to) { (_, Active) => "the first write to a 2-phase borrowed mutable reference", (_, Frozen) => "a loss of write permissions", - (Reserved { conflicted: false, .. }, Reserved { conflicted: true, .. }) => + (ReservedFrz { conflicted: false }, ReservedFrz { conflicted: true }) => "a temporary loss of write permissions until function exit", (Frozen, Disabled) => "a loss of read permissions", (_, Disabled) => "a loss of read and write permissions", @@ -380,28 +409,33 @@ pub mod diagnostics { (Frozen, Frozen) => true, (Active, Frozen) => true, (Disabled, Disabled) => true, - (Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) => - true, + ( + ReservedFrz { conflicted: true, .. }, + ReservedFrz { conflicted: true, .. }, + ) => true, // A pointer being `Disabled` is a strictly stronger source of // errors than it being `Frozen`. If we try to access a `Disabled`, // then where it became `Frozen` (or `Active` or `Reserved`) is the least // of our concerns for now. - (Reserved { conflicted: true, .. } | Active | Frozen, Disabled) => false, - (Reserved { conflicted: true, .. }, Frozen) => false, + (ReservedFrz { conflicted: true } | Active | Frozen, Disabled) => false, + (ReservedFrz { conflicted: true }, Frozen) => false, // `Active` and `Reserved` have all permissions, so a // `ChildAccessForbidden(Reserved | Active)` can never exist. - (_, Active) | (_, Reserved { conflicted: false, .. }) => + (_, Active) | (_, ReservedFrz { conflicted: false }) => unreachable!("this permission cannot cause an error"), - // No transition has `Reserved(conflicted=false)` as its `.to` unless it's a noop. - (Reserved { conflicted: false, .. }, _) => + // No transition has `Reserved { conflicted: false }` or `ReservedIM` + // as its `.to` unless it's a noop. + (ReservedFrz { conflicted: false } | ReservedIM, _) => unreachable!("self is a noop transition"), // All transitions produced in normal executions (using `apply_access`) // change permissions in the order `Reserved -> Active -> Frozen -> Disabled`. // We assume that the error was triggered on the same location that // the transition `self` applies to, so permissions found must be increasing // in the order `self.from < self.to <= insufficient.inner` - (Active | Frozen | Disabled, Reserved { .. }) | (Disabled, Frozen) => + (Active | Frozen | Disabled, ReservedFrz { .. } | ReservedIM) + | (Disabled, Frozen) + | (ReservedFrz { .. }, ReservedIM) => unreachable!("permissions between self and err must be increasing"), } } @@ -415,8 +449,10 @@ pub mod diagnostics { // conflicted. (Active, Active) => true, (Frozen, Frozen) => true, - (Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) => - true, + ( + ReservedFrz { conflicted: true, .. }, + ReservedFrz { conflicted: true, .. }, + ) => true, // If the error is a transition `Frozen -> Disabled`, then we don't really // care whether before that was `Reserved -> Active -> Frozen` or // `Frozen` directly. @@ -429,23 +465,23 @@ pub mod diagnostics { // -> Reserved { conflicted: true }` is inexistant or irrelevant, // and so is the `Reserved { conflicted: false } -> Active` (Active, Frozen) => false, - (Reserved { conflicted: true, .. }, _) => false, + (ReservedFrz { conflicted: true }, _) => false, (_, Disabled) => unreachable!( "permission that results in Disabled should not itself be Disabled in the first place" ), - // No transition has `Reserved { conflicted: false }` as its `.to` + // No transition has `Reserved { conflicted: false }` or `ReservedIM` as its `.to` // unless it's a noop. - (Reserved { conflicted: false, .. }, _) => + (ReservedFrz { conflicted: false } | ReservedIM, _) => unreachable!("self is a noop transition"), // Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`, // so permissions found must be increasing in the order // `self.from < self.to <= forbidden.from < forbidden.to`. - (Disabled, Reserved { .. } | Active | Frozen) - | (Frozen, Reserved { .. } | Active) - | (Active, Reserved { .. }) => + (Disabled, ReservedFrz { .. } | ReservedIM | Active | Frozen) + | (Frozen, ReservedFrz { .. } | ReservedIM | Active) + | (Active, ReservedFrz { .. } | ReservedIM) => unreachable!("permissions between self and err must be increasing"), } } @@ -466,9 +502,9 @@ pub mod diagnostics { #[cfg(test)] impl Permission { - pub fn is_reserved_with_conflicted(&self, expected_conflicted: bool) -> bool { + pub fn is_reserved_frz_with_conflicted(&self, expected_conflicted: bool) -> bool { match self.inner { - Reserved { conflicted, .. } => conflicted == expected_conflicted, + ReservedFrz { conflicted } => conflicted == expected_conflicted, _ => false, } } @@ -482,10 +518,9 @@ mod propagation_optimization_checks { impl Exhaustive for PermissionPriv { fn exhaustive() -> Box> { Box::new( - vec![Active, Frozen, Disabled].into_iter().chain( - <[bool; 2]>::exhaustive() - .map(|[ty_is_freeze, conflicted]| Reserved { ty_is_freeze, conflicted }), - ), + vec![Active, Frozen, Disabled, ReservedIM] + .into_iter() + .chain(::exhaustive().map(|conflicted| ReservedFrz { conflicted })), ) } } @@ -525,6 +560,9 @@ mod propagation_optimization_checks { // We thus eliminate from this test and all other tests // the case where the tag is initially unprotected and later becomes protected. precondition!(old_protected || !new_protected); + if old_protected { + precondition!(old.compatible_with_protector()); + } for (access, rel_pos) in <(AccessKind, AccessRelatedness)>::exhaustive() { if let Some(new) = perform_access(access, rel_pos, old, old_protected) { assert_eq!( @@ -546,6 +584,9 @@ mod propagation_optimization_checks { for old in PermissionPriv::exhaustive() { for [old_protected, new_protected] in <[bool; 2]>::exhaustive() { precondition!(old_protected || !new_protected); + if old_protected { + precondition!(old.compatible_with_protector()); + } for rel_pos in AccessRelatedness::exhaustive() { precondition!(rel_pos.is_foreign()); if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) { @@ -570,6 +611,9 @@ mod propagation_optimization_checks { reach.insert((start, start)); for (access, rel) in <(AccessKind, AccessRelatedness)>::exhaustive() { for prot in bool::exhaustive() { + if prot { + precondition!(start.compatible_with_protector()); + } if let Some(end) = transition::perform_access(access, rel, start, prot) { reach.insert((start, end)); } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs index 73717014e8995..654419c099d3c 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs @@ -14,6 +14,15 @@ impl Exhaustive for LocationState { } } +impl LocationState { + /// Ensure that the current internal state can exist at the same time as a protector. + /// In practice this only eliminates `ReservedIM` that is never used in the presence + /// of a protector (we instead emit `ReservedFrz` on retag). + pub fn compatible_with_protector(&self) -> bool { + self.permission.compatible_with_protector() + } +} + #[test] #[rustfmt::skip] // Exhaustive check that for any starting configuration loc, @@ -30,6 +39,9 @@ fn all_read_accesses_commute() { // so the two read accesses occur under the same protector. for protected in bool::exhaustive() { for loc in LocationState::exhaustive() { + if protected { + precondition!(loc.compatible_with_protector()); + } // Apply 1 then 2. Failure here means that there is UB in the source // and we skip the check in the target. let mut loc12 = loc; @@ -61,8 +73,8 @@ fn protected_enforces_noalias() { // We want to check pairs of accesses where one is foreign and one is not. precondition!(rel1.is_foreign() != rel2.is_foreign()); for [kind1, kind2] in <[AccessKind; 2]>::exhaustive() { - for mut state in LocationState::exhaustive() { - let protected = true; + let protected = true; + for mut state in LocationState::exhaustive().filter(|s| s.compatible_with_protector()) { precondition!(state.perform_access(kind1, rel1, protected).is_ok()); precondition!(state.perform_access(kind2, rel2, protected).is_ok()); // If these were both allowed, it must have been two reads. @@ -387,6 +399,9 @@ mod spurious_read { fn retag_y(self, new_y: LocStateProt) -> Result { assert!(new_y.is_initial()); + if new_y.prot && !new_y.state.compatible_with_protector() { + return Err(()); + } // `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`. Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self } .read_if_initialized(PtrSelector::Y) @@ -511,7 +526,7 @@ mod spurious_read { } #[test] - // `Reserved(aliased=false)` and `Reserved(aliased=true)` are properly indistinguishable + // `Reserved { conflicted: false }` and `Reserved { conflicted: true }` are properly indistinguishable // under the conditions where we want to insert a spurious read. fn reserved_aliased_protected_indistinguishable() { let source = LocStateProtPair { @@ -521,14 +536,16 @@ mod spurious_read { prot: true, }, y: LocStateProt { - state: LocationState::new_uninit(Permission::new_reserved(false)), + state: LocationState::new_uninit(Permission::new_reserved( + /* freeze */ true, /* protected */ true, + )), prot: true, }, }; let acc = TestAccess { ptr: PtrSelector::X, kind: AccessKind::Read }; let target = source.clone().perform_test_access(&acc).unwrap(); - assert!(source.y.state.permission.is_reserved_with_conflicted(false)); - assert!(target.y.state.permission.is_reserved_with_conflicted(true)); + assert!(source.y.state.permission.is_reserved_frz_with_conflicted(false)); + assert!(target.y.state.permission.is_reserved_frz_with_conflicted(true)); assert!(!source.distinguishable::<(), ()>(&target)) } @@ -563,7 +580,13 @@ mod spurious_read { precondition!(x_retag_perm.initialized); // And `x` just got retagged, so it must be initial. precondition!(x_retag_perm.permission.is_initial()); + // As stated earlier, `x` is always protected in the patterns we consider here. + precondition!(x_retag_perm.compatible_with_protector()); for y_protected in bool::exhaustive() { + // Finally `y` that is optionally protected must have a compatible permission. + if y_protected { + precondition!(y_current_perm.compatible_with_protector()); + } v.push(Pattern { xy_rel, x_retag_perm, y_current_perm, y_protected }); } } diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr index ce9a5b7f15865..133a50938f281 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr @@ -2,11 +2,11 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| RsM | └─┬── -| RsM | ├─┬── -| RsM | │ └─┬── -| Rs | │ └──── Strongly protected -| RsM | └──── +| ReIM| └─┬── +| ReIM| ├─┬── +| ReIM| │ └─┬── +| Res | │ └──── Strongly protected +| ReIM| └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) at ALLOC[0x0] is forbidden --> $DIR/cell-protected-write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr index 41559587bda33..a4dc123979edf 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr @@ -2,11 +2,11 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | ├─┬── -| Rs | │ └─┬── -| Rs | │ └──── Strongly protected -| Rs | └──── +| Res | └─┬── +| Res | ├─┬── +| Res | │ └─┬── +| Res | │ └──── Strongly protected +| Res | └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) at ALLOC[0x0] is forbidden --> $DIR/int-protected-write.rs:LL:CC diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr index 57caa09c88836..d13e9ad0215bc 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr @@ -2,7 +2,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| RsM | └──── +| ReIM| └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr index 69b8a17dc5e12..4d77d96776d31 100644 --- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr @@ -2,27 +2,27 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | └──── +| Res | └─┬── +| Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | └─┬── -| Rs | └─┬── -| Rs | └──── Strongly protected +| Res | └─┬── +| Res | └─┬── +| Res | └─┬── +| Res | └──── Strongly protected ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | ├─┬── -| Rs | │ └─┬── -| Rs | │ └──── -| Rs | └──── +| Res | └─┬── +| Res | ├─┬── +| Res | │ └─┬── +| Res | │ └──── +| Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr index 235ab68fe0178..29f99034bab5c 100644 --- a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr @@ -2,7 +2,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1.. 2.. 10.. 11.. 100.. 101..1000..1001..1024 | Act | Act | Act | Act | Act | Act | Act | Act | Act | └─┬── -| Rs | Act | Rs | Act | Rs | Act | Rs | Act | Rs | └─┬── +| Res | Act | Res | Act | Res | Act | Res | Act | Res | └─┬── |-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├──── |-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----| ├──── |-----|-----|-----|-----|-----| Frz |-----|?Dis |-----| ├──── diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr index f09aa52f1a10a..d589a06211182 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr @@ -11,5 +11,5 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Act | └─┬── | Act | └─┬── | Frz | ├──── -| Rs | └──── +| Res | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr index d149a4065f92b..be90382640b4a 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr @@ -3,20 +3,20 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| RsM | └─┬── -| RsM | ├─┬── -| RsM | │ └─┬── -| RsC | │ └──── -| RsM | └──── +| ReIM| └─┬── +| ReIM| ├─┬── +| ReIM| │ └─┬── +| ResC| │ └──── +| ReIM| └──── ────────────────────────────────────────────────── [interior mut] Foreign Read: Re* -> Re* ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 | Act | └─┬── -| RsM | └─┬── -| RsM | ├──── -| RsM | └──── +| ReIM| └─┬── +| ReIM| ├──── +| ReIM| └──── ────────────────────────────────────────────────── [interior mut] Foreign Write: Re* -> Re* ────────────────────────────────────────────────── @@ -24,7 +24,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 | Act | └─┬── | Act | └─┬── -| RsM | ├──── +| ReIM| ├──── | Act | └──── ────────────────────────────────────────────────── [protected] Foreign Read: Res -> Frz @@ -32,20 +32,20 @@ Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | ├─┬── -| Rs | │ └─┬── -| RsC | │ └──── -| Rs | └──── +| Res | └─┬── +| Res | ├─┬── +| Res | │ └─┬── +| ResC| │ └──── +| Res | └──── ────────────────────────────────────────────────── [] Foreign Read: Res -> Res ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | ├──── -| Rs | └──── +| Res | └─┬── +| Res | ├──── +| Res | └──── ────────────────────────────────────────────────── [] Foreign Write: Res -> Dis ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr index 6e774e5014d41..6098c855bde72 100644 --- a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr @@ -2,8 +2,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | └──── +| Res | └─┬── +| Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr index 26d9ad2ad3838..960c7e216e1d0 100644 --- a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr @@ -2,8 +2,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Rs | └─┬── -| Rs | └─┬── +| Res | └─┬── +| Res | └─┬── |-----| └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr index f63aa1f683497..254eba061f45d 100644 --- a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr @@ -2,5 +2,5 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 2 | Act | └─┬── -| Rs | └──── +| Res | └──── ────────────────────────────────────────────────── From 7643ea5b9c241019ecabb29b3d1eb30450dc2412 Mon Sep 17 00:00:00 2001 From: binarycat Date: Wed, 24 Jul 2024 15:14:41 -0400 Subject: [PATCH 013/685] create a new section on pointer to reference conversion also start deduplicating the docs that are getting moved to this section. --- library/core/src/ptr/mod.rs | 24 ++++++++++++++++++++ library/core/src/ptr/mut_ptr.rs | 40 ++++----------------------------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index f2247e83ec5c5..683f1d57f2259 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -56,6 +56,30 @@ //! has size 0, i.e., even if memory is not actually touched. Consider using //! [`NonNull::dangling`] in such cases. //! +//! ## Pointer to reference conversion +//! When converting a pointer to a reference using `&*`, there are several +//! rules that must be followed: +//! * The pointer must be properly aligned. +//! +//! * It must be "dereferenceable" in the sense defined above +//! +//! * The pointer must point to an initialized instance of `T`. +//! +//! * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is +//! arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. +//! In particular, while this reference exists, the memory the pointer points to must +//! not get accessed (read or written) through any other pointer. +//! +//! If a pointer follows all of these rules, it is said to be +//! *convertable to a reference*. +//! +//! These apply even if the result is unused! +//! (The part about being initialized is not yet fully decided, but until +//! it is, the only safe approach is to ensure that they are indeed initialized.) +//! +//! An example of the implications of the above rules is that an expression such +//! as `unsafe { &*(0 as *const u8) }` is Immediate Undefined Behavior. +//! //! ## Allocated object //! //! An *allocated object* is a subset of program memory which is addressable diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 904d6c62dcf1e..c88b356ec743c 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -247,24 +247,7 @@ impl *mut T { /// # Safety /// /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -609,25 +592,10 @@ impl *mut T { /// /// # Safety /// - /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get accessed (read or written) through any other pointer. + /// When calling this method, you have to ensure that *either* + /// the pointer is null *or* + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion) /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety /// /// # Examples /// From 1073f97ed8ed11bad047fc0b647ed99b98afb7ca Mon Sep 17 00:00:00 2001 From: binarycat Date: Wed, 24 Jul 2024 16:36:33 -0400 Subject: [PATCH 014/685] remove duplicate explanations of the ptr to ref conversion rules --- library/core/src/ptr/const_ptr.rs | 54 ++--------------------- library/core/src/ptr/mod.rs | 34 ++++++++++---- library/core/src/ptr/mut_ptr.rs | 73 ++++--------------------------- library/core/src/ptr/non_null.rs | 72 +++++------------------------- 4 files changed, 50 insertions(+), 183 deletions(-) diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 3e7933e9eec86..8b9f7b57d0053 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -239,24 +239,7 @@ impl *const T { /// # Safety /// /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -302,24 +285,8 @@ impl *const T { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety + /// When calling this method, you have to ensure that + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -350,20 +317,7 @@ impl *const T { /// # Safety /// /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// - /// [the module documentation]: crate::ptr#safety + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 683f1d57f2259..591705a69fdee 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -57,21 +57,39 @@ //! [`NonNull::dangling`] in such cases. //! //! ## Pointer to reference conversion -//! When converting a pointer to a reference using `&*`, there are several -//! rules that must be followed: +//! When converting a pointer to a reference `&T` using `&*`, +//! there are several rules that must be followed: +//! //! * The pointer must be properly aligned. //! -//! * It must be "dereferenceable" in the sense defined above +// some microprocessors may use address 0 for an interrupt vector. +// users of these microprocessors must always read/write address 0 through +// a raw pointer, not a reference. +//! * It must be non-null. +//! +//! * It must be "dereferenceable" in the sense defined above. //! -//! * The pointer must point to an initialized instance of `T`. +//! * The pointer must point to a valid instance of `T`. +//! This means that the created reference can only refer to +//! uninitialized memory through careful use of `MaybeUninit`. //! -//! * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is -//! arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. -//! In particular, while this reference exists, the memory the pointer points to must -//! not get accessed (read or written) through any other pointer. +//! * You must enforce Rust's aliasing rules, since the lifetime of the +//! created reference is arbitrarily chosen, +//! and does not necessarily reflect the actual lifetime of the data. +//! In particular, while this reference exists, +//! the memory the pointer points to must +//! not get accessed (read or written) through any raw pointer, +//! except for data inside an `UnsafeCell` +// ^ previous documentation was somewhat unclear on if modifications through +// an UnsafeCell are safe even if they would seemingly violate the exclusivity +// of a mut ref. //! //! If a pointer follows all of these rules, it is said to be //! *convertable to a reference*. +// ^ we use this term instead of saying that the produced reference must +// be valid, as the validity of a reference is easily confused for the +// validity of the thing it refers to, and while the two concepts are +// closly related, they are not identical. //! //! These apply even if the result is unused! //! (The part about being initialized is not yet fully decided, but until diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index c88b356ec743c..fb393ffadd5cd 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -296,24 +296,7 @@ impl *mut T { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety + /// When calling this method, you have to ensure that the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -347,20 +330,9 @@ impl *mut T { /// # Safety /// /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// - /// [the module documentation]: crate::ptr#safety + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion). + /// Note that because the created reference is to `MaybeUninit`, the + /// source pointer can point to uninitialized memory. /// /// # Examples /// @@ -594,7 +566,7 @@ impl *mut T { /// /// When calling this method, you have to ensure that *either* /// the pointer is null *or* - /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion) + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// /// # Examples @@ -643,24 +615,8 @@ impl *mut T { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) - /// - /// [the module documentation]: crate::ptr#safety + /// When calling this method, you have to ensure that + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -695,20 +651,7 @@ impl *mut T { /// # Safety /// /// When calling this method, you have to ensure that *either* the pointer is null *or* - /// all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get accessed (read or written) through any other pointer. - /// - /// This applies even if the result of this method is unused! - /// - /// [the module documentation]: crate::ptr#safety + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) #[inline] #[unstable(feature = "ptr_as_uninit", issue = "75402")] #[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")] diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index 796c85d0cacc7..1e1cf780c28f0 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -128,20 +128,10 @@ impl NonNull { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// - /// [the module documentation]: crate::ptr#safety + /// When calling this method, you have to ensure that + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion). + /// Note that because the created reference is to `MaybeUninit`, the + /// source pointer can point to uninitialized memory. #[inline] #[must_use] #[unstable(feature = "ptr_as_uninit", issue = "75402")] @@ -162,20 +152,10 @@ impl NonNull { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get accessed (read or written) through any other pointer. - /// - /// This applies even if the result of this method is unused! - /// - /// [the module documentation]: crate::ptr#safety + /// When calling this method, you have to ensure that + /// the pointer is [convirtible to a reference](crate::ptr#pointer-to-reference-conversion). + /// Note that because the created reference is to `MaybeUninit`, the + /// source pointer can point to uninitialized memory. #[inline] #[must_use] #[unstable(feature = "ptr_as_uninit", issue = "75402")] @@ -361,22 +341,8 @@ impl NonNull { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get mutated (except inside `UnsafeCell`). - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) + /// When calling this method, you have to ensure that + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// /// # Examples /// @@ -412,22 +378,8 @@ impl NonNull { /// /// # Safety /// - /// When calling this method, you have to ensure that all of the following is true: - /// - /// * The pointer must be properly aligned. - /// - /// * It must be "dereferenceable" in the sense defined in [the module documentation]. - /// - /// * The pointer must point to an initialized instance of `T`. - /// - /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is - /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. - /// In particular, while this reference exists, the memory the pointer points to must - /// not get accessed (read or written) through any other pointer. - /// - /// This applies even if the result of this method is unused! - /// (The part about being initialized is not yet fully decided, but until - /// it is, the only safe approach is to ensure that they are indeed initialized.) + /// When calling this method, you have to ensure that + /// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion) /// # Examples /// /// ``` From 3877a7bcf3176740b49c94a137b233e88ce0a401 Mon Sep 17 00:00:00 2001 From: binarycat Date: Thu, 25 Jul 2024 11:53:07 -0400 Subject: [PATCH 015/685] clarify interactions with MaybeUninit and UnsafeCell --- library/core/src/ptr/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 591705a69fdee..b2fb365d22745 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -69,9 +69,12 @@ //! //! * It must be "dereferenceable" in the sense defined above. //! -//! * The pointer must point to a valid instance of `T`. +//! * The pointer must point to a valid value of type `T`. //! This means that the created reference can only refer to -//! uninitialized memory through careful use of `MaybeUninit`. +//! uninitialized memory through careful use of `MaybeUninit`, +//! or if the uninitialized memory is entirly contained within +//! padding bytes, since +//! [padding has the same validity invariant as `MaybeUninit`][ucg-pad]. //! //! * You must enforce Rust's aliasing rules, since the lifetime of the //! created reference is arbitrarily chosen, @@ -79,10 +82,9 @@ //! In particular, while this reference exists, //! the memory the pointer points to must //! not get accessed (read or written) through any raw pointer, -//! except for data inside an `UnsafeCell` -// ^ previous documentation was somewhat unclear on if modifications through -// an UnsafeCell are safe even if they would seemingly violate the exclusivity -// of a mut ref. +//! except for data inside an `UnsafeCell`. +//! Note that aliased writes are always UB for mutable references, +//! even if they only modify `UnsafeCell` data. //! //! If a pointer follows all of these rules, it is said to be //! *convertable to a reference*. @@ -98,6 +100,8 @@ //! An example of the implications of the above rules is that an expression such //! as `unsafe { &*(0 as *const u8) }` is Immediate Undefined Behavior. //! +//! [ucgpad]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#padding +//! //! ## Allocated object //! //! An *allocated object* is a subset of program memory which is addressable From 760c3fab4f0acff98ee32f05a0726c88e11fe814 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jul 2024 11:12:35 +0200 Subject: [PATCH 016/685] Fix case where `doc_markdown` is triggered on words ending with "ified" --- clippy_lints/src/doc/markdown.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 41c0bcd55adca..237badb3f233c 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -92,6 +92,10 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b && matches!(prefix.chars().last(), Some('S' | 'X')) { prefix + } else if let Some(prefix) = s.strip_suffix("ified") + && prefix.chars().all(|c| c.is_ascii_uppercase()) + { + prefix } else { s.strip_suffix('s').unwrap_or(s) }; From 88506a9147172ff233f820451982e9514aee3476 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jul 2024 11:12:59 +0200 Subject: [PATCH 017/685] Add regression test for #13097 --- tests/ui/doc/doc_markdown-issue_13097.fixed | 13 +++++++++++++ tests/ui/doc/doc_markdown-issue_13097.rs | 13 +++++++++++++ tests/ui/doc/doc_markdown-issue_13097.stderr | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/ui/doc/doc_markdown-issue_13097.fixed create mode 100644 tests/ui/doc/doc_markdown-issue_13097.rs create mode 100644 tests/ui/doc/doc_markdown-issue_13097.stderr diff --git a/tests/ui/doc/doc_markdown-issue_13097.fixed b/tests/ui/doc/doc_markdown-issue_13097.fixed new file mode 100644 index 0000000000000..fb0f40b34a4b8 --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.fixed @@ -0,0 +1,13 @@ +// This test checks that words starting with capital letters and ending with "ified" don't +// trigger the lint. + +#![deny(clippy::doc_markdown)] + +pub enum OutputFormat { + /// `HumaNified` + //~^ ERROR: item in documentation is missing backticks + Plain, + // Should not warn! + /// JSONified console output + Json, +} diff --git a/tests/ui/doc/doc_markdown-issue_13097.rs b/tests/ui/doc/doc_markdown-issue_13097.rs new file mode 100644 index 0000000000000..8c1e1a3cd6c24 --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.rs @@ -0,0 +1,13 @@ +// This test checks that words starting with capital letters and ending with "ified" don't +// trigger the lint. + +#![deny(clippy::doc_markdown)] + +pub enum OutputFormat { + /// HumaNified + //~^ ERROR: item in documentation is missing backticks + Plain, + // Should not warn! + /// JSONified console output + Json, +} diff --git a/tests/ui/doc/doc_markdown-issue_13097.stderr b/tests/ui/doc/doc_markdown-issue_13097.stderr new file mode 100644 index 0000000000000..ae68a767ec930 --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.stderr @@ -0,0 +1,18 @@ +error: item in documentation is missing backticks + --> tests/ui/doc/doc_markdown-issue_13097.rs:7:9 + | +LL | /// HumaNified + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/doc/doc_markdown-issue_13097.rs:4:9 + | +LL | #![deny(clippy::doc_markdown)] + | ^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | /// `HumaNified` + | ~~~~~~~~~~~~ + +error: aborting due to 1 previous error + From 855a9d1377a2da3e4566de9adb8b783295a771dd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 24 Jun 2024 18:42:23 +0200 Subject: [PATCH 018/685] Add new `too_long_first_doc_paragraph` first paragraph lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/doc/mod.rs | 113 +++++++++++++----- .../src/doc/too_long_first_doc_paragraph.rs | 85 +++++++++++++ .../ui/too_long_first_doc_paragraph-fix.fixed | 8 ++ tests/ui/too_long_first_doc_paragraph-fix.rs | 7 ++ .../too_long_first_doc_paragraph-fix.stderr | 19 +++ tests/ui/too_long_first_doc_paragraph.rs | 47 ++++++++ tests/ui/too_long_first_doc_paragraph.stderr | 22 ++++ 9 files changed, 270 insertions(+), 33 deletions(-) create mode 100644 clippy_lints/src/doc/too_long_first_doc_paragraph.rs create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.fixed create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.rs create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.stderr create mode 100644 tests/ui/too_long_first_doc_paragraph.rs create mode 100644 tests/ui/too_long_first_doc_paragraph.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c03b03d9be3..ca70da5bb5170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5913,6 +5913,7 @@ Released 2018-09-13 [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args [`to_string_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl [`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`too_long_first_doc_paragraph`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph [`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments [`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines [`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 69f9eb6842bcd..2933a65a71f58 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -148,6 +148,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::doc::NEEDLESS_DOCTEST_MAIN_INFO, crate::doc::SUSPICIOUS_DOC_COMMENTS_INFO, crate::doc::TEST_ATTR_IN_DOCTEST_INFO, + crate::doc::TOO_LONG_FIRST_DOC_PARAGRAPH_INFO, crate::doc::UNNECESSARY_SAFETY_DOC_INFO, crate::double_parens::DOUBLE_PARENS_INFO, crate::drop_forget_ref::DROP_NON_DROP_INFO, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5b6a5b08aa94c..5e9fb0162bf80 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1,4 +1,6 @@ mod lazy_continuation; +mod too_long_first_doc_paragraph; + use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; @@ -422,6 +424,38 @@ declare_clippy_lint! { "require every line of a paragraph to be indented and marked" } +declare_clippy_lint! { + /// ### What it does + /// Checks if the first line in the documentation of items listed in module page is too long. + /// + /// ### Why is this bad? + /// Documentation will show the first paragraph of the doscstring in the summary page of a + /// module, so having a nice, short summary in the first paragraph is part of writing good docs. + /// + /// ### Example + /// ```no_run + /// /// A very short summary. + /// /// A much longer explanation that goes into a lot more detail about + /// /// how the thing works, possibly with doclinks and so one, + /// /// and probably spanning a many rows. + /// struct Foo {} + /// ``` + /// Use instead: + /// ```no_run + /// /// A very short summary. + /// /// + /// /// A much longer explanation that goes into a lot more detail about + /// /// how the thing works, possibly with doclinks and so one, + /// /// and probably spanning a many rows. + /// struct Foo {} + /// ``` + #[clippy::version = "1.81.0"] + pub TOO_LONG_FIRST_DOC_PARAGRAPH, + style, + "ensure that the first line of a documentation paragraph isn't too long" +} + +#[derive(Clone)] pub struct Documentation { valid_idents: &'static FxHashSet, check_private_items: bool, @@ -448,6 +482,7 @@ impl_lint_pass!(Documentation => [ SUSPICIOUS_DOC_COMMENTS, EMPTY_DOCS, DOC_LAZY_CONTINUATION, + TOO_LONG_FIRST_DOC_PARAGRAPH, ]); impl<'tcx> LateLintPass<'tcx> for Documentation { @@ -457,39 +492,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { }; match cx.tcx.hir_node(cx.last_node_with_lint_attrs) { - Node::Item(item) => match item.kind { - ItemKind::Fn(sig, _, body_id) => { - if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { - let body = cx.tcx.hir().body(body_id); - - let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); - missing_headers::check( + Node::Item(item) => { + too_long_first_doc_paragraph::check(cx, attrs, item.kind, headers.first_paragraph_len); + match item.kind { + ItemKind::Fn(sig, _, body_id) => { + if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) + || in_external_macro(cx.tcx.sess, item.span)) + { + let body = cx.tcx.hir().body(body_id); + + let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); + missing_headers::check( + cx, + item.owner_id, + sig, + headers, + Some(body_id), + panic_info, + self.check_private_items, + ); + } + }, + ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) { + (false, Safety::Unsafe) => span_lint( cx, - item.owner_id, - sig, - headers, - Some(body_id), - panic_info, - self.check_private_items, - ); - } - }, - ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) { - (false, Safety::Unsafe) => span_lint( - cx, - MISSING_SAFETY_DOC, - cx.tcx.def_span(item.owner_id), - "docs for unsafe trait missing `# Safety` section", - ), - (true, Safety::Safe) => span_lint( - cx, - UNNECESSARY_SAFETY_DOC, - cx.tcx.def_span(item.owner_id), - "docs for safe trait have unnecessary `# Safety` section", - ), + MISSING_SAFETY_DOC, + cx.tcx.def_span(item.owner_id), + "docs for unsafe trait missing `# Safety` section", + ), + (true, Safety::Safe) => span_lint( + cx, + UNNECESSARY_SAFETY_DOC, + cx.tcx.def_span(item.owner_id), + "docs for safe trait have unnecessary `# Safety` section", + ), + _ => (), + }, _ => (), - }, - _ => (), + } }, Node::TraitItem(trait_item) => { if let TraitItemKind::Fn(sig, ..) = trait_item.kind @@ -547,6 +587,7 @@ struct DocHeaders { safety: bool, errors: bool, panics: bool, + first_paragraph_len: usize, } /// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and @@ -586,8 +627,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ acc }); doc.pop(); + let doc = doc.trim(); - if doc.trim().is_empty() { + if doc.is_empty() { if let Some(span) = span_of_fragments(&fragments) { span_lint_and_help( cx, @@ -611,7 +653,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ cx, valid_idents, parser.into_offset_iter(), - &doc, + doc, Fragments { fragments: &fragments, doc: &doc, @@ -653,6 +695,7 @@ fn check_doc<'a, Events: Iterator, Range, Range { if let End(TagEnd::Heading(_)) = event { diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs new file mode 100644 index 0000000000000..a5a58b444011a --- /dev/null +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -0,0 +1,85 @@ +use rustc_ast::ast::Attribute; +use rustc_errors::Applicability; +use rustc_hir::ItemKind; +use rustc_lint::LateContext; + +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::source::snippet_opt; + +use super::TOO_LONG_FIRST_DOC_PARAGRAPH; + +pub(super) fn check( + cx: &LateContext<'_>, + attrs: &[Attribute], + item_kind: ItemKind<'_>, + mut first_paragraph_len: usize, +) { + if first_paragraph_len <= 100 + || !matches!( + item_kind, + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::Fn(..) + | ItemKind::Macro(..) + | ItemKind::Mod(..) + | ItemKind::TyAlias(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) + | ItemKind::Trait(..) + | ItemKind::TraitAlias(..) + ) + { + return; + } + let mut spans = Vec::new(); + let mut should_suggest_empty_doc = false; + + for attr in attrs { + if let Some(doc) = attr.doc_str() { + spans.push(attr.span); + let doc = doc.as_str(); + let doc = doc.trim(); + if spans.len() == 1 { + // We make this suggestion only if the first doc line ends with a punctuation + // because if might just need to add an empty line with `///`. + should_suggest_empty_doc = doc.ends_with('.') || doc.ends_with('!') || doc.ends_with('?'); + } + let len = doc.chars().count(); + if len >= first_paragraph_len { + break; + } + first_paragraph_len -= len; + } + } + + let &[first_span, .., last_span] = spans.as_slice() else { return }; + + if should_suggest_empty_doc + && let Some(second_span) = spans.get(1) + && let new_span = first_span.with_hi(second_span.lo()).with_lo(first_span.hi()) + && let Some(snippet) = snippet_opt(cx, new_span) + { + span_lint_and_then( + cx, + TOO_LONG_FIRST_DOC_PARAGRAPH, + first_span.with_hi(last_span.lo()), + "first doc comment paragraph is too long", + |diag| { + diag.span_suggestion( + new_span, + "add", + format!("{snippet}///\n"), + Applicability::MachineApplicable, + ); + }, + ); + return; + } + span_lint( + cx, + TOO_LONG_FIRST_DOC_PARAGRAPH, + first_span.with_hi(last_span.lo()), + "first doc comment paragraph is too long", + ); +} diff --git a/tests/ui/too_long_first_doc_paragraph-fix.fixed b/tests/ui/too_long_first_doc_paragraph-fix.fixed new file mode 100644 index 0000000000000..b3f66f5279384 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.fixed @@ -0,0 +1,8 @@ +#![warn(clippy::too_long_first_doc_paragraph)] + +/// A very short summary. +/// +/// A much longer explanation that goes into a lot more detail about +/// how the thing works, possibly with doclinks and so one, +/// and probably spanning a many rows. +struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.rs b/tests/ui/too_long_first_doc_paragraph-fix.rs new file mode 100644 index 0000000000000..f0ece9523de4b --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.rs @@ -0,0 +1,7 @@ +#![warn(clippy::too_long_first_doc_paragraph)] + +/// A very short summary. +/// A much longer explanation that goes into a lot more detail about +/// how the thing works, possibly with doclinks and so one, +/// and probably spanning a many rows. +struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.stderr b/tests/ui/too_long_first_doc_paragraph-fix.stderr new file mode 100644 index 0000000000000..00949f405d538 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.stderr @@ -0,0 +1,19 @@ +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph-fix.rs:3:1 + | +LL | / /// A very short summary. +LL | | /// A much longer explanation that goes into a lot more detail about +LL | | /// how the thing works, possibly with doclinks and so one, +LL | | /// and probably spanning a many rows. + | |_ + | + = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::too_long_first_doc_paragraph)]` +help: add an empty line + | +LL ~ /// A very short summary. +LL + /// + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/too_long_first_doc_paragraph.rs b/tests/ui/too_long_first_doc_paragraph.rs new file mode 100644 index 0000000000000..88a8f6d38310d --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph.rs @@ -0,0 +1,47 @@ +//@no-rustfix + +#![warn(clippy::too_long_first_doc_paragraph)] + +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +pub struct Bar; + +// Should not warn! (not an item visible on mod page) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +impl Bar {} + +// Should not warn! (less than 80 characters) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. +/// +/// Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +enum Enum { + A, +} + +/// Lorem +/// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +union Union { + a: u8, + b: u8, +} + +// Should not warn! (title) +/// # bla +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +union Union2 { + a: u8, + b: u8, +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/too_long_first_doc_paragraph.stderr b/tests/ui/too_long_first_doc_paragraph.stderr new file mode 100644 index 0000000000000..7f48e5cf884e6 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph.stderr @@ -0,0 +1,22 @@ +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph.rs:5:1 + | +LL | / /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +LL | | /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +LL | | /// gravida non lacinia at, rhoncus eu lacus. + | |_ + | + = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::too_long_first_doc_paragraph)]` + +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph.rs:26:1 + | +LL | / /// Lorem +LL | | /// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +LL | | /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +LL | | /// gravida non lacinia at, rhoncus eu lacus. + | |_ + +error: aborting due to 2 previous errors + From f455587feec19b459c5eb75c9dc1995b93bb24f6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 9 Jul 2024 16:00:06 +0200 Subject: [PATCH 019/685] Set the limit of characters to 200 and don't run the lint on private items unless config allows it --- clippy_lints/src/doc/mod.rs | 13 +++++++++---- .../src/doc/too_long_first_doc_paragraph.rs | 17 +++++++++++------ tests/ui/too_long_first_doc_paragraph-fix.fixed | 5 +++-- tests/ui/too_long_first_doc_paragraph-fix.rs | 5 +++-- .../ui/too_long_first_doc_paragraph-fix.stderr | 3 ++- tests/ui/too_long_first_doc_paragraph.rs | 12 +++++++++--- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5e9fb0162bf80..6debcd9b6a0f1 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -493,7 +493,13 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { match cx.tcx.hir_node(cx.last_node_with_lint_attrs) { Node::Item(item) => { - too_long_first_doc_paragraph::check(cx, attrs, item.kind, headers.first_paragraph_len); + too_long_first_doc_paragraph::check( + cx, + item, + attrs, + headers.first_paragraph_len, + self.check_private_items, + ); match item.kind { ItemKind::Fn(sig, _, body_id) => { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) @@ -627,9 +633,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ acc }); doc.pop(); - let doc = doc.trim(); - if doc.is_empty() { + if doc.trim().is_empty() { if let Some(span) = span_of_fragments(&fragments) { span_lint_and_help( cx, @@ -653,7 +658,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ cx, valid_idents, parser.into_offset_iter(), - doc, + &doc, Fragments { fragments: &fragments, doc: &doc, diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index a5a58b444011a..a1b714b7d0020 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -1,6 +1,6 @@ use rustc_ast::ast::Attribute; use rustc_errors::Applicability; -use rustc_hir::ItemKind; +use rustc_hir::{Item, ItemKind}; use rustc_lint::LateContext; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; @@ -10,13 +10,17 @@ use super::TOO_LONG_FIRST_DOC_PARAGRAPH; pub(super) fn check( cx: &LateContext<'_>, + item: &Item<'_>, attrs: &[Attribute], - item_kind: ItemKind<'_>, mut first_paragraph_len: usize, + check_private_items: bool, ) { - if first_paragraph_len <= 100 + if !check_private_items && !cx.effective_visibilities.is_exported(item.owner_id.def_id) { + return; + } + if first_paragraph_len <= 200 || !matches!( - item_kind, + item.kind, ItemKind::Static(..) | ItemKind::Const(..) | ItemKind::Fn(..) @@ -32,6 +36,7 @@ pub(super) fn check( { return; } + let mut spans = Vec::new(); let mut should_suggest_empty_doc = false; @@ -42,7 +47,7 @@ pub(super) fn check( let doc = doc.trim(); if spans.len() == 1 { // We make this suggestion only if the first doc line ends with a punctuation - // because if might just need to add an empty line with `///`. + // because it might just need to add an empty line with `///`. should_suggest_empty_doc = doc.ends_with('.') || doc.ends_with('!') || doc.ends_with('?'); } let len = doc.chars().count(); @@ -68,7 +73,7 @@ pub(super) fn check( |diag| { diag.span_suggestion( new_span, - "add", + "add an empty line", format!("{snippet}///\n"), Applicability::MachineApplicable, ); diff --git a/tests/ui/too_long_first_doc_paragraph-fix.fixed b/tests/ui/too_long_first_doc_paragraph-fix.fixed index b3f66f5279384..d4a0cdf3447f1 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.fixed +++ b/tests/ui/too_long_first_doc_paragraph-fix.fixed @@ -4,5 +4,6 @@ /// /// A much longer explanation that goes into a lot more detail about /// how the thing works, possibly with doclinks and so one, -/// and probably spanning a many rows. -struct Foo; +/// and probably spanning a many rows. Blablabla, it needs to be over +/// 200 characters so I needed to write something longeeeeeeer. +pub struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.rs b/tests/ui/too_long_first_doc_paragraph-fix.rs index f0ece9523de4b..5a3b6c42a328b 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.rs +++ b/tests/ui/too_long_first_doc_paragraph-fix.rs @@ -3,5 +3,6 @@ /// A very short summary. /// A much longer explanation that goes into a lot more detail about /// how the thing works, possibly with doclinks and so one, -/// and probably spanning a many rows. -struct Foo; +/// and probably spanning a many rows. Blablabla, it needs to be over +/// 200 characters so I needed to write something longeeeeeeer. +pub struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.stderr b/tests/ui/too_long_first_doc_paragraph-fix.stderr index 00949f405d538..6403265a39c54 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.stderr +++ b/tests/ui/too_long_first_doc_paragraph-fix.stderr @@ -4,7 +4,8 @@ error: first doc comment paragraph is too long LL | / /// A very short summary. LL | | /// A much longer explanation that goes into a lot more detail about LL | | /// how the thing works, possibly with doclinks and so one, -LL | | /// and probably spanning a many rows. +LL | | /// and probably spanning a many rows. Blablabla, it needs to be over +LL | | /// 200 characters so I needed to write something longeeeeeeer. | |_ | = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` diff --git a/tests/ui/too_long_first_doc_paragraph.rs b/tests/ui/too_long_first_doc_paragraph.rs index 88a8f6d38310d..1042249c5b7bd 100644 --- a/tests/ui/too_long_first_doc_paragraph.rs +++ b/tests/ui/too_long_first_doc_paragraph.rs @@ -19,7 +19,7 @@ impl Bar {} /// Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -enum Enum { +pub enum Enum { A, } @@ -27,7 +27,7 @@ enum Enum { /// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -union Union { +pub union Union { a: u8, b: u8, } @@ -37,11 +37,17 @@ union Union { /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -union Union2 { +pub union Union2 { a: u8, b: u8, } +// Should not warn! (not public) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +fn f() {} + fn main() { // test code goes here } From 4969960a9c0a9ac4e8bbe0083124186eb5159028 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 9 Jul 2024 16:29:39 +0200 Subject: [PATCH 020/685] Fix dogfood lints --- .../src/doc/too_long_first_doc_paragraph.rs | 4 +++- clippy_utils/src/lib.rs | 21 ++++++++++++------- clippy_utils/src/macros.rs | 9 ++++---- clippy_utils/src/source.rs | 14 +++++++------ clippy_utils/src/ty.rs | 20 +++++++++++------- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index a1b714b7d0020..45ec392d55380 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -58,7 +58,9 @@ pub(super) fn check( } } - let &[first_span, .., last_span] = spans.as_slice() else { return }; + let &[first_span, .., last_span] = spans.as_slice() else { + return; + }; if should_suggest_empty_doc && let Some(second_span) = spans.get(1) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1d5f1a2a2bb13..7d613a50665f4 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -150,6 +150,7 @@ macro_rules! extract_msrv_attr { /// If the given expression is a local binding, find the initializer expression. /// If that initializer expression is another local binding, find its initializer again. +/// /// This process repeats as long as possible (but usually no more than once). Initializer /// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`] /// instead. @@ -180,6 +181,7 @@ pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr } /// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable. +/// /// By only considering immutable bindings, we guarantee that the returned expression represents the /// value of the binding wherever it is referenced. /// @@ -428,12 +430,12 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator( path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) } -/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the +/// THIS METHOD IS DEPRECATED. Matches a `Path` against a slice of segment string literals. +/// +/// This method is deprecated and will eventually be removed since it does not match against the /// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from /// `QPath::Resolved.1.res.opt_def_id()`. /// -/// Matches a `Path` against a slice of segment string literals. -/// /// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a /// `rustc_hir::Path`. /// @@ -903,6 +905,7 @@ pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> } /// Returns true if the expr is equal to `Default::default()` of it's type when evaluated. +/// /// It doesn't cover all cases, for example indirect function calls (some of std /// functions are supported) but it is the best we have. pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { @@ -1059,6 +1062,7 @@ impl std::ops::BitOrAssign for CaptureKind { } /// Given an expression referencing a local, determines how it would be captured in a closure. +/// /// Note as this will walk up to parent expressions until the capture can be determined it should /// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or /// function argument (other than a receiver). @@ -2361,8 +2365,9 @@ pub fn fn_def_id_with_node_args<'tcx>( } /// Returns `Option` where String is a textual representation of the type encapsulated in -/// the slice iff the given expression is a slice of primitives (as defined in the -/// `is_recursively_primitive_type` function) and `None` otherwise. +/// the slice iff the given expression is a slice of primitives. +/// +/// (As defined in the `is_recursively_primitive_type` function.) Returns `None` otherwise. pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { let expr_type = cx.typeck_results().expr_ty_adjusted(expr); let expr_kind = expr_type.kind(); diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 455239cc37f35..3c85eb1296e1f 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -150,10 +150,11 @@ pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> } /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the -/// macro call site (i.e. the parent of the macro expansion). This generally means that `node` -/// is the outermost node of an entire macro expansion, but there are some caveats noted below. -/// This is useful for finding macro calls while visiting the HIR without processing the macro call -/// at every node within its expansion. +/// macro call site (i.e. the parent of the macro expansion). +/// +/// This generally means that `node` is the outermost node of an entire macro expansion, but there +/// are some caveats noted below. This is useful for finding macro calls while visiting the HIR +/// without processing the macro call at every node within its expansion. /// /// If you already have immediate access to the parent node, it is simpler to /// just check the context of that span directly (e.g. `parent.span.from_expansion()`). diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 496c8f5b55373..924cdf1b8735c 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -527,9 +527,10 @@ pub fn snippet_block_with_context<'a>( (reindent_multiline(snip, true, indent), from_macro) } -/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This -/// will result in the macro call, rather than the expansion, if the span is from a child context. -/// If the span is not from a child context, it will be used directly instead. +/// Same as `snippet_with_applicability`, but first walks the span up to the given context. +/// +/// This will result in the macro call, rather than the expansion, if the span is from a child +/// context. If the span is not from a child context, it will be used directly instead. /// /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node /// would result in `box []`. If given the context of the address of expression, this function will @@ -572,9 +573,10 @@ fn snippet_with_context_sess<'a>( } /// Walks the span up to the target context, thereby returning the macro call site if the span is -/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the -/// case of the span being in a macro expansion, but the target context is from expanding a macro -/// argument. +/// inside a macro expansion, or the original span if it is not. +/// +/// Note this will return `None` in the case of the span being in a macro expansion, but the target +/// context is from expanding a macro argument. /// /// Given the following /// diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 812fb647fdab6..de5ae484834cc 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -160,8 +160,10 @@ pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option, ty: Ty<'_>) -> bool { matches!( get_type_diagnostic_name(cx, ty), @@ -398,8 +400,10 @@ fn is_normalizable_helper<'tcx>( } /// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any -/// integer or floating-point number type). For checking aggregation of primitive types (e.g. -/// tuples and slices of primitive type) see `is_recursively_primitive_type` +/// integer or floating-point number type). +/// +/// For checking aggregation of primitive types (e.g. tuples and slices of primitive type) see +/// `is_recursively_primitive_type` pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_)) } @@ -476,9 +480,10 @@ pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { } } -/// Checks if the drop order for a type matters. Some std types implement drop solely to -/// deallocate memory. For these types, and composites containing them, changing the drop order -/// won't result in any observable side effects. +/// Checks if the drop order for a type matters. +/// +/// Some std types implement drop solely to deallocate memory. For these types, and composites +/// containing them, changing the drop order won't result in any observable side effects. pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { fn needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet>) -> bool { if !seen.insert(ty) { @@ -1324,6 +1329,7 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl } /// Checks if a Ty<'_> has some inherent method Symbol. +/// /// This does not look for impls in the type's `Deref::Target` type. /// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`. pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> { From 3c6e5ef4ae20c7439a2bc951ef349c59954412c6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 21 Jul 2024 17:29:32 +0200 Subject: [PATCH 021/685] Add comment explaining what the matching items are for `TOO_LONG_FIRST_DOC_PARAGRAPH` lint --- clippy_lints/src/doc/too_long_first_doc_paragraph.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index 45ec392d55380..a47cba64b28e5 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -21,6 +21,8 @@ pub(super) fn check( if first_paragraph_len <= 200 || !matches!( item.kind, + // This is the list of items which can be documented AND are displayed on the module + // page. So associated items or impl blocks are not part of this list. ItemKind::Static(..) | ItemKind::Const(..) | ItemKind::Fn(..) From 0b87af9d4f7c6faa9e89496609f016dc3e3977e1 Mon Sep 17 00:00:00 2001 From: Mrmaxmeier Date: Sat, 27 Apr 2024 23:14:36 +0200 Subject: [PATCH 022/685] Add `-Z embed-source=yes` to embed source code in DWARF debug info --- .../src/debuginfo/metadata.rs | 9 +++++++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 2 ++ compiler/rustc_interface/src/tests.rs | 1 + .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 9 +++++-- compiler/rustc_session/messages.ftl | 6 +++++ compiler/rustc_session/src/errors.rs | 14 +++++++++++ compiler/rustc_session/src/options.rs | 2 ++ compiler/rustc_session/src/session.rs | 25 +++++++++++++++++-- .../src/compiler-flags/embed-source.md | 12 +++++++++ 9 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/embed-source.md diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index ad63858861261..701ea62b21a7d 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -629,6 +629,9 @@ pub fn file_metadata<'ll>(cx: &CodegenCx<'ll, '_>, source_file: &SourceFile) -> }; let hash_value = hex_encode(source_file.src_hash.hash_bytes()); + let source = + cx.sess().opts.unstable_opts.embed_source.then_some(()).and(source_file.src.as_ref()); + unsafe { llvm::LLVMRustDIBuilderCreateFile( DIB(cx), @@ -639,6 +642,8 @@ pub fn file_metadata<'ll>(cx: &CodegenCx<'ll, '_>, source_file: &SourceFile) -> hash_kind, hash_value.as_ptr().cast(), hash_value.len(), + source.map_or(ptr::null(), |x| x.as_ptr().cast()), + source.map_or(0, |x| x.len()), ) } } @@ -659,6 +664,8 @@ pub fn unknown_file_metadata<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll DIFile { llvm::ChecksumKind::None, hash_value.as_ptr().cast(), hash_value.len(), + ptr::null(), + 0, ) }) } @@ -943,6 +950,8 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>( llvm::ChecksumKind::None, ptr::null(), 0, + ptr::null(), + 0, ); let unit_metadata = llvm::LLVMRustDIBuilderCreateCompileUnit( diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index c8e0e075eeabc..faa675b66c8a1 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1853,6 +1853,8 @@ extern "C" { CSKind: ChecksumKind, Checksum: *const c_char, ChecksumLen: size_t, + Source: *const c_char, + SourceLen: size_t, ) -> &'a DIFile; pub fn LLVMRustDIBuilderCreateSubroutineType<'a>( diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index ce3b2f77f210a..c4704e38ce6fa 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -773,6 +773,7 @@ fn test_unstable_options_tracking_hash() { tracked!(direct_access_external_data, Some(true)); tracked!(dual_proc_macros, true); tracked!(dwarf_version, Some(5)); + tracked!(embed_source, true); tracked!(emit_thin_lto, false); tracked!(export_executable_symbols, true); tracked!(fewer_names, Some(true)); diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 4cdd8af1008c0..6e700c31e6763 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -901,14 +901,19 @@ extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateFile(LLVMRustDIBuilderRef Builder, const char *Filename, size_t FilenameLen, const char *Directory, size_t DirectoryLen, LLVMRustChecksumKind CSKind, - const char *Checksum, size_t ChecksumLen) { + const char *Checksum, size_t ChecksumLen, + const char *Source, size_t SourceLen) { std::optional llvmCSKind = fromRust(CSKind); std::optional> CSInfo{}; if (llvmCSKind) CSInfo.emplace(*llvmCSKind, StringRef{Checksum, ChecksumLen}); + std::optional oSource{}; + if (Source) + oSource = StringRef(Source, SourceLen); return wrap(Builder->createFile(StringRef(Filename, FilenameLen), - StringRef(Directory, DirectoryLen), CSInfo)); + StringRef(Directory, DirectoryLen), CSInfo, + oSource)); } extern "C" LLVMMetadataRef diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index b84280a3ccf3f..afd5360c81194 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -14,6 +14,12 @@ session_crate_name_empty = crate name must not be empty session_crate_name_invalid = crate names cannot start with a `-`, but `{$s}` has a leading hyphen +session_embed_source_insufficient_dwarf_version = `-Zembed-source=y` requires at least `-Z dwarf-version=5` but DWARF version is {$dwarf_version} + +session_embed_source_requires_debug_info = `-Zembed-source=y` requires debug information to be enabled + +session_embed_source_requires_llvm_backend = `-Zembed-source=y` is only supported on the LLVM codegen backend + session_expr_parentheses_needed = parentheses are required to parse this as an expression session_failed_to_create_profiler = failed to create profiler: {$err} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 5cc54a5855bbe..f708109b87a0c 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -165,6 +165,20 @@ pub(crate) struct UnsupportedDwarfVersion { pub(crate) dwarf_version: u32, } +#[derive(Diagnostic)] +#[diag(session_embed_source_insufficient_dwarf_version)] +pub(crate) struct EmbedSourceInsufficientDwarfVersion { + pub(crate) dwarf_version: u32, +} + +#[derive(Diagnostic)] +#[diag(session_embed_source_requires_debug_info)] +pub(crate) struct EmbedSourceRequiresDebugInfo; + +#[derive(Diagnostic)] +#[diag(session_embed_source_requires_llvm_backend)] +pub(crate) struct EmbedSourceRequiresLLVMBackend; + #[derive(Diagnostic)] #[diag(session_target_stack_protector_not_supported)] pub(crate) struct StackProtectorNotSupportedForTarget<'a> { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index bf54aae1cfeb0..13aac6669fe4f 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1708,6 +1708,8 @@ options! { them only if an error has not been emitted"), ehcont_guard: bool = (false, parse_bool, [TRACKED], "generate Windows EHCont Guard tables"), + embed_source: bool = (false, parse_bool, [TRACKED], + "embed source text in DWARF debug sections (default: no)"), emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], "emit a section containing stack size metadata (default: no)"), emit_thin_lto: bool = (true, parse_bool, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index be67baf57f6dc..634f3684b51aa 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -37,8 +37,9 @@ use rustc_target::spec::{ use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::{ - self, CoverageLevel, CrateType, ErrorOutputType, FunctionReturn, Input, InstrumentCoverage, - OptLevel, OutFileName, OutputType, RemapPathScopeComponents, SwitchWithOptPath, + self, CoverageLevel, CrateType, DebugInfo, ErrorOutputType, FunctionReturn, Input, + InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents, + SwitchWithOptPath, }; use crate::parse::{add_feature_diagnostics, ParseSess}; use crate::search_paths::{PathKind, SearchPath}; @@ -1300,6 +1301,26 @@ fn validate_commandline_args_with_session_available(sess: &Session) { .emit_err(errors::SplitDebugInfoUnstablePlatform { debuginfo: sess.split_debuginfo() }); } + if sess.opts.unstable_opts.embed_source { + let dwarf_version = + sess.opts.unstable_opts.dwarf_version.unwrap_or(sess.target.default_dwarf_version); + + let uses_llvm_backend = + matches!(sess.opts.unstable_opts.codegen_backend.as_deref(), None | Some("llvm")); + + if dwarf_version < 5 { + sess.dcx().emit_warn(errors::EmbedSourceInsufficientDwarfVersion { dwarf_version }); + } + + if sess.opts.debuginfo == DebugInfo::None { + sess.dcx().emit_warn(errors::EmbedSourceRequiresDebugInfo); + } + + if !uses_llvm_backend { + sess.dcx().emit_warn(errors::EmbedSourceRequiresLLVMBackend); + } + } + if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray { sess.dcx().emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() }); } diff --git a/src/doc/unstable-book/src/compiler-flags/embed-source.md b/src/doc/unstable-book/src/compiler-flags/embed-source.md new file mode 100644 index 0000000000000..01a11e3779712 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/embed-source.md @@ -0,0 +1,12 @@ +# `embed-source` + +This flag controls whether the compiler embeds the program source code text into +the object debug information section. It takes one of the following values: + +* `y`, `yes`, `on` or `true`: put source code in debug info. +* `n`, `no`, `off`, `false` or no value: omit source code from debug info (the default). + +This flag is ignored in configurations that don't emit DWARF debug information +and is ignored on non-LLVM backends. `-Z embed-source` requires DWARFv5. Use +`-Z dwarf-version=5` to control the compiler's DWARF target version and `-g` to +enable debug info generation. From 608901b9c07d7d2f3e2803378c4f0cc07c61bc36 Mon Sep 17 00:00:00 2001 From: Mrmaxmeier Date: Tue, 16 Jul 2024 20:50:28 +0200 Subject: [PATCH 023/685] Add run-make test for -Zembed-source=yes --- tests/run-make/embed-source-dwarf/main.rs | 2 + tests/run-make/embed-source-dwarf/rmake.rs | 70 ++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/run-make/embed-source-dwarf/main.rs create mode 100644 tests/run-make/embed-source-dwarf/rmake.rs diff --git a/tests/run-make/embed-source-dwarf/main.rs b/tests/run-make/embed-source-dwarf/main.rs new file mode 100644 index 0000000000000..c80af84f41415 --- /dev/null +++ b/tests/run-make/embed-source-dwarf/main.rs @@ -0,0 +1,2 @@ +// hello +fn main() {} diff --git a/tests/run-make/embed-source-dwarf/rmake.rs b/tests/run-make/embed-source-dwarf/rmake.rs new file mode 100644 index 0000000000000..06d550121b0de --- /dev/null +++ b/tests/run-make/embed-source-dwarf/rmake.rs @@ -0,0 +1,70 @@ +//@ ignore-windows +//@ ignore-apple + +// LLVM 17's embed-source implementation requires that source code is attached +// for all files in the output DWARF debug info. This restriction was lifted in +// LLVM 18 (87e22bdd2bd6d77d782f9d64b3e3ae5bdcd5080d). +//@ min-llvm-version: 18 + +// This test should be replaced with one in tests/debuginfo once we can easily +// tell via GDB or LLDB if debuginfo contains source code. Cheap tricks in LLDB +// like setting an invalid source map path don't appear to work, maybe this'll +// become easier once GDB supports DWARFv6? + +use std::collections::HashMap; +use std::path::PathBuf; +use std::rc::Rc; + +use gimli::{AttributeValue, EndianRcSlice, Reader, RunTimeEndian}; +use object::{Object, ObjectSection}; +use run_make_support::{gimli, object, rfs, rustc}; + +fn main() { + let output = PathBuf::from("embed-source-main"); + rustc() + .input("main.rs") + .output(&output) + .arg("-g") + .arg("-Zembed-source=yes") + .arg("-Zdwarf-version=5") + .run(); + let output = rfs::read(output); + let obj = object::File::parse(output.as_slice()).unwrap(); + let endian = if obj.is_little_endian() { RunTimeEndian::Little } else { RunTimeEndian::Big }; + let dwarf = gimli::Dwarf::load(|section| -> Result<_, ()> { + let data = obj.section_by_name(section.name()).map(|s| s.uncompressed_data().unwrap()); + Ok(EndianRcSlice::new(Rc::from(data.unwrap_or_default().as_ref()), endian)) + }) + .unwrap(); + + let mut sources = HashMap::new(); + + let mut iter = dwarf.units(); + while let Some(header) = iter.next().unwrap() { + let unit = dwarf.unit(header).unwrap(); + let unit = unit.unit_ref(&dwarf); + + if let Some(program) = &unit.line_program { + let header = program.header(); + for file in header.file_names() { + if let Some(source) = file.source() { + let path = unit + .attr_string(file.path_name()) + .unwrap() + .to_string_lossy() + .unwrap() + .to_string(); + let source = + unit.attr_string(source).unwrap().to_string_lossy().unwrap().to_string(); + if !source.is_empty() { + sources.insert(path, source); + } + } + } + } + } + + dbg!(&sources); + assert_eq!(sources.len(), 1); + assert_eq!(sources.get("main.rs").unwrap(), "// hello\nfn main() {}\n"); +} From ec921db289ea87fd4030cb7f8a70f6ba3a31c2c7 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Sun, 23 Jun 2024 22:11:35 +0300 Subject: [PATCH 024/685] impl CloneToUninit for str and CStr --- library/core/src/clone.rs | 21 +++++++++++++++++++ library/core/tests/clone.rs | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 76a89eaaff86e..409fd2746f588 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -346,6 +346,27 @@ unsafe impl CloneToUninit for [T] { } } +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for str { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: str is just a [u8] with UTF-8 invariant + unsafe { self.as_bytes().clone_to_uninit(dst as *mut [u8]) } + } +} + +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for crate::ffi::CStr { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: For now, CStr is just a #[repr(trasnsparent)] [c_char] with some invariants. + // And we can cast [c_char] to [u8] on all supported platforms (see: to_bytes_with_nul). + // The pointer metadata properly preserves the length (NUL included). + // See: `cstr_metadata_is_length_with_nul` in tests. + unsafe { self.to_bytes_with_nul().clone_to_uninit(dst as *mut [u8]) } + } +} + /// Ownership of a collection of values stored in a non-owned `[MaybeUninit]`, some of which /// are not yet initialized. This is sort of like a `Vec` that doesn't own its allocation. /// Its responsibility is to provide cleanup on unwind by dropping the values that *are* diff --git a/library/core/tests/clone.rs b/library/core/tests/clone.rs index b7130f16f8795..71a328733b7c4 100644 --- a/library/core/tests/clone.rs +++ b/library/core/tests/clone.rs @@ -1,5 +1,7 @@ use core::clone::CloneToUninit; +use core::ffi::CStr; use core::mem::MaybeUninit; +use core::ptr; #[test] #[allow(suspicious_double_ref_op)] @@ -81,3 +83,41 @@ fn test_clone_to_uninit_slice_drops_on_panic() { drop(a); assert_eq!(COUNTER.load(Relaxed), 0); } + +#[test] +fn test_clone_to_uninit_str() { + let a = "hello"; + + let mut storage: MaybeUninit<[u8; 5]> = MaybeUninit::uninit(); + unsafe { a.clone_to_uninit(storage.as_mut_ptr() as *mut [u8] as *mut str) }; + assert_eq!(a.as_bytes(), unsafe { storage.assume_init() }.as_slice()); + + let mut b: Box = "world".into(); + assert_eq!(a.len(), b.len()); + assert_ne!(a, &*b); + unsafe { a.clone_to_uninit(ptr::from_mut::(&mut b)) }; + assert_eq!(a, &*b); +} + +#[test] +fn test_clone_to_uninit_cstr() { + let a = c"hello"; + + let mut storage: MaybeUninit<[u8; 6]> = MaybeUninit::uninit(); + unsafe { a.clone_to_uninit(storage.as_mut_ptr() as *mut [u8] as *mut CStr) }; + assert_eq!(a.to_bytes_with_nul(), unsafe { storage.assume_init() }.as_slice()); + + let mut b: Box = c"world".into(); + assert_eq!(a.count_bytes(), b.count_bytes()); + assert_ne!(a, &*b); + unsafe { a.clone_to_uninit(ptr::from_mut::(&mut b)) }; + assert_eq!(a, &*b); +} + +#[test] +fn cstr_metadata_is_length_with_nul() { + let s: &CStr = c"abcdef"; + let p: *const CStr = ptr::from_ref(s); + let bytes: *const [u8] = p as *const [u8]; + assert_eq!(s.to_bytes_with_nul().len(), bytes.len()); +} From afabc583f7f646d45f506263a1c331383ebdc252 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Sun, 23 Jun 2024 23:05:10 +0300 Subject: [PATCH 025/685] impl CloneToUninit for Path and OsStr --- library/std/src/ffi/os_str.rs | 12 ++++++++++++ library/std/src/ffi/os_str/tests.rs | 17 +++++++++++++++++ library/std/src/lib.rs | 1 + library/std/src/path.rs | 11 +++++++++++ library/std/src/path/tests.rs | 19 +++++++++++++++++++ library/std/src/sys/os_str/bytes.rs | 12 ++++++++++++ library/std/src/sys/os_str/wtf8.rs | 12 ++++++++++++ library/std/src/sys_common/wtf8.rs | 11 +++++++++++ 8 files changed, 95 insertions(+) diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index a501bcc98cf38..f68ea3c562a5e 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -3,10 +3,13 @@ #[cfg(test)] mod tests; +use core::clone::CloneToUninit; + use crate::borrow::{Borrow, Cow}; use crate::collections::TryReserveError; use crate::hash::{Hash, Hasher}; use crate::ops::{self, Range}; +use crate::ptr::addr_of_mut; use crate::rc::Rc; use crate::str::FromStr; use crate::sync::Arc; @@ -1261,6 +1264,15 @@ impl Clone for Box { } } +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for OsStr { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: we're just a wrapper around a platform-specific Slice + unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) } + } +} + #[stable(feature = "shared_from_slice2", since = "1.24.0")] impl From for Arc { /// Converts an [`OsString`] into an [Arc]<[OsStr]> by moving the [`OsString`] diff --git a/library/std/src/ffi/os_str/tests.rs b/library/std/src/ffi/os_str/tests.rs index 5b39b9e34d8c7..67147934b4db3 100644 --- a/library/std/src/ffi/os_str/tests.rs +++ b/library/std/src/ffi/os_str/tests.rs @@ -1,4 +1,6 @@ use super::*; +use crate::mem::MaybeUninit; +use crate::ptr; #[test] fn test_os_string_with_capacity() { @@ -286,3 +288,18 @@ fn slice_surrogate_edge() { assert_eq!(post_crab.slice_encoded_bytes(..4), "🦀"); assert_eq!(post_crab.slice_encoded_bytes(4..), surrogate); } + +#[test] +fn clone_to_uninit() { + let a = OsStr::new("hello.txt"); + + let mut storage = vec![MaybeUninit::::uninit(); size_of_val::(a)]; + unsafe { a.clone_to_uninit(ptr::from_mut::<[_]>(storage.as_mut_slice()) as *mut OsStr) }; + assert_eq!(a.as_encoded_bytes(), unsafe { MaybeUninit::slice_assume_init_ref(&storage) }); + + let mut b: Box = OsStr::new("world.exe").into(); + assert_eq!(size_of_val::(a), size_of_val::(&b)); + assert_ne!(a, &*b); + unsafe { a.clone_to_uninit(ptr::from_mut::(&mut b)) }; + assert_eq!(a, &*b); +} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ee6f5a6f3c0d5..5cc6376a32d72 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -323,6 +323,7 @@ // tidy-alphabetical-start #![feature(c_str_module)] #![feature(char_internals)] +#![feature(clone_to_uninit)] #![feature(core_intrinsics)] #![feature(core_io_borrowed_buf)] #![feature(duration_constants)] diff --git a/library/std/src/path.rs b/library/std/src/path.rs index 80163667636ae..d6c78883f2804 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -70,6 +70,8 @@ #[cfg(test)] mod tests; +use core::clone::CloneToUninit; + use crate::borrow::{Borrow, Cow}; use crate::collections::TryReserveError; use crate::error::Error; @@ -3109,6 +3111,15 @@ impl Path { } } +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for Path { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: Path is just a wrapper around OsStr + unsafe { self.inner.clone_to_uninit(core::ptr::addr_of_mut!((*dst).inner)) } + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl AsRef for Path { #[inline] diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs index a12e42cba0c5c..6436872087d6c 100644 --- a/library/std/src/path/tests.rs +++ b/library/std/src/path/tests.rs @@ -3,6 +3,8 @@ use core::hint::black_box; use super::*; use crate::collections::{BTreeSet, HashSet}; use crate::hash::DefaultHasher; +use crate::mem::MaybeUninit; +use crate::ptr; #[allow(unknown_lints, unused_macro_rules)] macro_rules! t ( @@ -2054,3 +2056,20 @@ fn bench_hash_path_long(b: &mut test::Bencher) { black_box(hasher.finish()); } + +#[test] +fn clone_to_uninit() { + let a = Path::new("hello.txt"); + + let mut storage = vec![MaybeUninit::::uninit(); size_of_val::(a)]; + unsafe { a.clone_to_uninit(ptr::from_mut::<[_]>(storage.as_mut_slice()) as *mut Path) }; + assert_eq!(a.as_os_str().as_encoded_bytes(), unsafe { + MaybeUninit::slice_assume_init_ref(&storage) + }); + + let mut b: Box = Path::new("world.exe").into(); + assert_eq!(size_of_val::(a), size_of_val::(&b)); + assert_ne!(a, &*b); + unsafe { a.clone_to_uninit(ptr::from_mut::(&mut b)) }; + assert_eq!(a, &*b); +} diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs index 0f8bd6453528e..8529207366e93 100644 --- a/library/std/src/sys/os_str/bytes.rs +++ b/library/std/src/sys/os_str/bytes.rs @@ -1,6 +1,9 @@ //! The underlying OsString/OsStr implementation on Unix and many other //! systems: just a `Vec`/`[u8]`. +use core::clone::CloneToUninit; +use core::ptr::addr_of_mut; + use crate::borrow::Cow; use crate::collections::TryReserveError; use crate::fmt::Write; @@ -345,3 +348,12 @@ impl Slice { self.inner.eq_ignore_ascii_case(&other.inner) } } + +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for Slice { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: we're just a wrapper around [u8] + unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) } + } +} diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs index ed975ba58b5e2..e5755a4b87443 100644 --- a/library/std/src/sys/os_str/wtf8.rs +++ b/library/std/src/sys/os_str/wtf8.rs @@ -1,5 +1,8 @@ //! The underlying OsString/OsStr implementation on Windows is a //! wrapper around the "WTF-8" encoding; see the `wtf8` module for more. +use core::clone::CloneToUninit; +use core::ptr::addr_of_mut; + use crate::borrow::Cow; use crate::collections::TryReserveError; use crate::rc::Rc; @@ -268,3 +271,12 @@ impl Slice { self.inner.eq_ignore_ascii_case(&other.inner) } } + +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for Slice { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: we're just a wrapper around Wtf8 + unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) } + } +} diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs index 277c9506febbb..2bdeff78ddfd6 100644 --- a/library/std/src/sys_common/wtf8.rs +++ b/library/std/src/sys_common/wtf8.rs @@ -19,12 +19,14 @@ mod tests; use core::char::{encode_utf16_raw, encode_utf8_raw}; +use core::clone::CloneToUninit; use core::str::next_code_point; use crate::borrow::Cow; use crate::collections::TryReserveError; use crate::hash::{Hash, Hasher}; use crate::iter::FusedIterator; +use crate::ptr::addr_of_mut; use crate::rc::Rc; use crate::sync::Arc; use crate::sys_common::AsInner; @@ -1046,3 +1048,12 @@ impl Hash for Wtf8 { 0xfeu8.hash(state) } } + +#[unstable(feature = "clone_to_uninit", issue = "126799")] +unsafe impl CloneToUninit for Wtf8 { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_to_uninit(&self, dst: *mut Self) { + // SAFETY: we're just a wrapper around [u8] + unsafe { self.bytes.clone_to_uninit(addr_of_mut!((*dst).bytes)) } + } +} From dbc13fb309f3a1539e8bb1cdeeb5fbb2e3eaaa43 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Mon, 24 Jun 2024 18:09:27 +0300 Subject: [PATCH 026/685] Sparkle some attributes over `CloneToUninit` stuff --- library/core/src/clone.rs | 7 +++++++ library/std/src/ffi/os_str.rs | 1 + library/std/src/path.rs | 1 + library/std/src/sys/os_str/bytes.rs | 1 + library/std/src/sys/os_str/wtf8.rs | 1 + library/std/src/sys_common/wtf8.rs | 1 + 6 files changed, 12 insertions(+) diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 409fd2746f588..88f7990c017df 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -272,6 +272,7 @@ pub unsafe trait CloneToUninit { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for T { + #[inline] default unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of // ptr::write(). @@ -285,8 +286,10 @@ unsafe impl CloneToUninit for T { // Specialized implementation for types that are [`Copy`], not just [`Clone`], // and can therefore be copied bitwise. +#[doc(hidden)] #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for T { + #[inline] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of // ptr::copy_nonoverlapping(). @@ -298,6 +301,7 @@ unsafe impl CloneToUninit for T { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for [T] { + #[inline] #[cfg_attr(debug_assertions, track_caller)] default unsafe fn clone_to_uninit(&self, dst: *mut Self) { let len = self.len(); @@ -326,8 +330,10 @@ unsafe impl CloneToUninit for [T] { } } +#[doc(hidden)] #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for [T] { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { let len = self.len(); @@ -348,6 +354,7 @@ unsafe impl CloneToUninit for [T] { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for str { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: str is just a [u8] with UTF-8 invariant diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index f68ea3c562a5e..918eec2d0d8ef 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -1266,6 +1266,7 @@ impl Clone for Box { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for OsStr { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: we're just a wrapper around a platform-specific Slice diff --git a/library/std/src/path.rs b/library/std/src/path.rs index d6c78883f2804..9eaa0e01c2c00 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -3113,6 +3113,7 @@ impl Path { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for Path { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: Path is just a wrapper around OsStr diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs index 8529207366e93..992767211d083 100644 --- a/library/std/src/sys/os_str/bytes.rs +++ b/library/std/src/sys/os_str/bytes.rs @@ -351,6 +351,7 @@ impl Slice { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for Slice { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: we're just a wrapper around [u8] diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs index e5755a4b87443..433237aa6e7bf 100644 --- a/library/std/src/sys/os_str/wtf8.rs +++ b/library/std/src/sys/os_str/wtf8.rs @@ -274,6 +274,7 @@ impl Slice { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for Slice { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: we're just a wrapper around Wtf8 diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs index 2bdeff78ddfd6..063451ad54e1c 100644 --- a/library/std/src/sys_common/wtf8.rs +++ b/library/std/src/sys_common/wtf8.rs @@ -1051,6 +1051,7 @@ impl Hash for Wtf8 { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for Wtf8 { + #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { // SAFETY: we're just a wrapper around [u8] From 110c273f4fb40c318be59a557ba90314fbbc42a6 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Wed, 26 Jun 2024 02:22:07 +0300 Subject: [PATCH 027/685] CloneToUninit: use a private specialization trait and move implementation details into a submodule --- library/core/src/clone.rs | 123 ++--------------------------- library/core/src/clone/uninit.rs | 128 +++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 117 deletions(-) create mode 100644 library/core/src/clone/uninit.rs diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 88f7990c017df..2150463067205 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -36,8 +36,7 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::mem::{self, MaybeUninit}; -use crate::ptr; +mod uninit; /// A common trait for the ability to explicitly duplicate an object. /// @@ -248,7 +247,7 @@ pub unsafe trait CloneToUninit { /// * `dst` must be properly aligned. /// * `dst` must have the same [pointer metadata] (slice length or `dyn` vtable) as `self`. /// - /// [valid]: ptr#safety + /// [valid]: crate::ptr#safety /// [pointer metadata]: crate::ptr::metadata() /// /// # Panics @@ -272,83 +271,20 @@ pub unsafe trait CloneToUninit { #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for T { - #[inline] - default unsafe fn clone_to_uninit(&self, dst: *mut Self) { - // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of - // ptr::write(). - unsafe { - // We hope the optimizer will figure out to create the cloned value in-place, - // skipping ever storing it on the stack and the copy to the destination. - ptr::write(dst, self.clone()); - } - } -} - -// Specialized implementation for types that are [`Copy`], not just [`Clone`], -// and can therefore be copied bitwise. -#[doc(hidden)] -#[unstable(feature = "clone_to_uninit", issue = "126799")] -unsafe impl CloneToUninit for T { #[inline] unsafe fn clone_to_uninit(&self, dst: *mut Self) { - // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of - // ptr::copy_nonoverlapping(). - unsafe { - ptr::copy_nonoverlapping(self, dst, 1); - } + // SAFETY: we're calling a specialization with the same contract + unsafe { ::clone_one(self, dst) } } } #[unstable(feature = "clone_to_uninit", issue = "126799")] unsafe impl CloneToUninit for [T] { - #[inline] - #[cfg_attr(debug_assertions, track_caller)] - default unsafe fn clone_to_uninit(&self, dst: *mut Self) { - let len = self.len(); - // This is the most likely mistake to make, so check it as a debug assertion. - debug_assert_eq!( - len, - dst.len(), - "clone_to_uninit() source and destination must have equal lengths", - ); - - // SAFETY: The produced `&mut` is valid because: - // * The caller is obligated to provide a pointer which is valid for writes. - // * All bytes pointed to are in MaybeUninit, so we don't care about the memory's - // initialization status. - let uninit_ref = unsafe { &mut *(dst as *mut [MaybeUninit]) }; - - // Copy the elements - let mut initializing = InitializingSlice::from_fully_uninit(uninit_ref); - for element_ref in self.iter() { - // If the clone() panics, `initializing` will take care of the cleanup. - initializing.push(element_ref.clone()); - } - // If we reach here, then the entire slice is initialized, and we've satisfied our - // responsibilities to the caller. Disarm the cleanup guard by forgetting it. - mem::forget(initializing); - } -} - -#[doc(hidden)] -#[unstable(feature = "clone_to_uninit", issue = "126799")] -unsafe impl CloneToUninit for [T] { #[inline] #[cfg_attr(debug_assertions, track_caller)] unsafe fn clone_to_uninit(&self, dst: *mut Self) { - let len = self.len(); - // This is the most likely mistake to make, so check it as a debug assertion. - debug_assert_eq!( - len, - dst.len(), - "clone_to_uninit() source and destination must have equal lengths", - ); - - // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of - // ptr::copy_nonoverlapping(). - unsafe { - ptr::copy_nonoverlapping(self.as_ptr(), dst.as_mut_ptr(), len); - } + // SAFETY: we're calling a specialization with the same contract + unsafe { ::clone_slice(self, dst) } } } @@ -374,53 +310,6 @@ unsafe impl CloneToUninit for crate::ffi::CStr { } } -/// Ownership of a collection of values stored in a non-owned `[MaybeUninit]`, some of which -/// are not yet initialized. This is sort of like a `Vec` that doesn't own its allocation. -/// Its responsibility is to provide cleanup on unwind by dropping the values that *are* -/// initialized, unless disarmed by forgetting. -/// -/// This is a helper for `impl CloneToUninit for [T]`. -struct InitializingSlice<'a, T> { - data: &'a mut [MaybeUninit], - /// Number of elements of `*self.data` that are initialized. - initialized_len: usize, -} - -impl<'a, T> InitializingSlice<'a, T> { - #[inline] - fn from_fully_uninit(data: &'a mut [MaybeUninit]) -> Self { - Self { data, initialized_len: 0 } - } - - /// Push a value onto the end of the initialized part of the slice. - /// - /// # Panics - /// - /// Panics if the slice is already fully initialized. - #[inline] - fn push(&mut self, value: T) { - MaybeUninit::write(&mut self.data[self.initialized_len], value); - self.initialized_len += 1; - } -} - -impl<'a, T> Drop for InitializingSlice<'a, T> { - #[cold] // will only be invoked on unwind - fn drop(&mut self) { - let initialized_slice = ptr::slice_from_raw_parts_mut( - MaybeUninit::slice_as_mut_ptr(self.data), - self.initialized_len, - ); - // SAFETY: - // * the pointer is valid because it was made from a mutable reference - // * `initialized_len` counts the initialized elements as an invariant of this type, - // so each of the pointed-to elements is initialized and may be dropped. - unsafe { - ptr::drop_in_place::<[T]>(initialized_slice); - } - } -} - /// Implementations of `Clone` for primitive types. /// /// Implementations that cannot be described in Rust diff --git a/library/core/src/clone/uninit.rs b/library/core/src/clone/uninit.rs new file mode 100644 index 0000000000000..8b738bec796de --- /dev/null +++ b/library/core/src/clone/uninit.rs @@ -0,0 +1,128 @@ +use crate::mem::{self, MaybeUninit}; +use crate::ptr; + +/// Private specialization trait used by CloneToUninit, as per +/// [the dev guide](https://std-dev-guide.rust-lang.org/policy/specialization.html). +pub(super) unsafe trait CopySpec: Clone { + unsafe fn clone_one(src: &Self, dst: *mut Self); + unsafe fn clone_slice(src: &[Self], dst: *mut [Self]); +} + +unsafe impl CopySpec for T { + #[inline] + default unsafe fn clone_one(src: &Self, dst: *mut Self) { + // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of + // ptr::write(). + unsafe { + // We hope the optimizer will figure out to create the cloned value in-place, + // skipping ever storing it on the stack and the copy to the destination. + ptr::write(dst, src.clone()); + } + } + + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + default unsafe fn clone_slice(src: &[Self], dst: *mut [Self]) { + let len = src.len(); + // This is the most likely mistake to make, so check it as a debug assertion. + debug_assert_eq!( + len, + dst.len(), + "clone_to_uninit() source and destination must have equal lengths", + ); + + // SAFETY: The produced `&mut` is valid because: + // * The caller is obligated to provide a pointer which is valid for writes. + // * All bytes pointed to are in MaybeUninit, so we don't care about the memory's + // initialization status. + let uninit_ref = unsafe { &mut *(dst as *mut [MaybeUninit]) }; + + // Copy the elements + let mut initializing = InitializingSlice::from_fully_uninit(uninit_ref); + for element_ref in src { + // If the clone() panics, `initializing` will take care of the cleanup. + initializing.push(element_ref.clone()); + } + // If we reach here, then the entire slice is initialized, and we've satisfied our + // responsibilities to the caller. Disarm the cleanup guard by forgetting it. + mem::forget(initializing); + } +} + +// Specialized implementation for types that are [`Copy`], not just [`Clone`], +// and can therefore be copied bitwise. +unsafe impl CopySpec for T { + #[inline] + unsafe fn clone_one(src: &Self, dst: *mut Self) { + // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of + // ptr::copy_nonoverlapping(). + unsafe { + ptr::copy_nonoverlapping(src, dst, 1); + } + } + + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn clone_slice(src: &[Self], dst: *mut [Self]) { + let len = src.len(); + // This is the most likely mistake to make, so check it as a debug assertion. + debug_assert_eq!( + len, + dst.len(), + "clone_to_uninit() source and destination must have equal lengths", + ); + + // SAFETY: The safety conditions of clone_to_uninit() are a superset of those of + // ptr::copy_nonoverlapping(). + unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), len); + } + } +} + +/// Ownership of a collection of values stored in a non-owned `[MaybeUninit]`, some of which +/// are not yet initialized. This is sort of like a `Vec` that doesn't own its allocation. +/// Its responsibility is to provide cleanup on unwind by dropping the values that *are* +/// initialized, unless disarmed by forgetting. +/// +/// This is a helper for `impl CloneToUninit for [T]`. +struct InitializingSlice<'a, T> { + data: &'a mut [MaybeUninit], + /// Number of elements of `*self.data` that are initialized. + initialized_len: usize, +} + +impl<'a, T> InitializingSlice<'a, T> { + #[inline] + fn from_fully_uninit(data: &'a mut [MaybeUninit]) -> Self { + Self { data, initialized_len: 0 } + } + + /// Push a value onto the end of the initialized part of the slice. + /// + /// # Panics + /// + /// Panics if the slice is already fully initialized. + #[inline] + fn push(&mut self, value: T) { + MaybeUninit::write(&mut self.data[self.initialized_len], value); + self.initialized_len += 1; + } +} + +impl<'a, T> Drop for InitializingSlice<'a, T> { + #[cold] // will only be invoked on unwind + fn drop(&mut self) { + let initialized_slice = ptr::slice_from_raw_parts_mut( + MaybeUninit::slice_as_mut_ptr(self.data), + self.initialized_len, + ); + // SAFETY: + // * the pointer is valid because it was made from a mutable reference + // * `initialized_len` counts the initialized elements as an invariant of this type, + // so each of the pointed-to elements is initialized and may be dropped. + unsafe { + ptr::drop_in_place::<[T]>(initialized_slice); + } + } +} From b7e7975dedb42edf3ba1b53708950db34c31c182 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 30 Jul 2024 16:01:36 +0200 Subject: [PATCH 028/685] Move style into its file --- .github/deploy.sh | 1 + util/gh-pages/index.html | 366 +-------------------------------------- util/gh-pages/style.css | 364 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 365 deletions(-) create mode 100644 util/gh-pages/style.css diff --git a/.github/deploy.sh b/.github/deploy.sh index 5a59f94ec918b..d937661c0f828 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -10,6 +10,7 @@ mkdir out/master/ cp util/gh-pages/index.html out/master cp util/gh-pages/script.js out/master cp util/gh-pages/lints.json out/master +cp util/gh-pages/style.css out/master if [[ -n $TAG_NAME ]]; then echo "Save the doc for the current tag ($TAG_NAME) and point stable/ to it" diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 7f271ac838597..267354cc8bfc6 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -23,371 +23,7 @@ - - +
diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css new file mode 100644 index 0000000000000..f9feb0ba13ae3 --- /dev/null +++ b/util/gh-pages/style.css @@ -0,0 +1,364 @@ +blockquote { font-size: 1em; } + +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} + +.dropdown-menu { + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); +} + +.dropdown-menu .divider { + background-color: var(--theme-popup-border); +} + +.dropdown-menu .checkbox { + display: block; + white-space: nowrap; + margin: 0; +} +.dropdown-menu .checkbox label { + padding: 3px 20px; + width: 100%; +} + +.dropdown-menu .checkbox input { + position: relative; + margin: 0 0.5rem 0; + padding: 0; +} + +.dropdown-menu .checkbox:hover { + background-color: var(--theme-hover); +} + +div.panel div.panel-body button { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); +} + +div.panel div.panel-body button:hover { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +div.panel div.panel-body button.open { + filter: brightness(90%); +} + +.dropdown-toggle .badge { + background-color: #777; +} + +.panel-heading { cursor: pointer; } + +.panel-title { display: flex; flex-wrap: wrap;} +.panel-title .label { display: inline-block; } + +.panel-title-name { flex: 1; min-width: 400px;} +.panel-title-name span { vertical-align: bottom; } + +.panel .panel-title-name .anchor { display: none; } +.panel:hover .panel-title-name .anchor { display: inline;} + +.search-control { + margin-top: 15px; +} + +@media (min-width: 992px) { + .search-control { + margin-top: 0; + } +} + +@media (min-width: 405px) { + #upper-filters { + display: flex; + flex-wrap: wrap; + } +} + +@media (max-width: 430px) { + /* Turn the version filter list to the left */ + #version-filter-selector { + right: 0; + left: auto; + } +} + +@media (max-width: 412px) { + #upper-filters, + .panel-body .search-control { + padding-right: 8px; + padding-left: 8px; + } +} + +.label { + padding-top: 0.3em; + padding-bottom: 0.3em; +} + +.label-lint-group { + min-width: 8em; +} +.label-lint-level { + min-width: 4em; +} + +.label-lint-level-allow { + background-color: #5cb85c; +} +.label-lint-level-warn { + background-color: #f0ad4e; +} +.label-lint-level-deny { + background-color: #d9534f; +} +.label-lint-level-none { + background-color: #777777; + opacity: 0.5; +} + +.label-group-deprecated { + opacity: 0.5; +} + +.label-doc-folding { + color: #000; + background-color: #fff; + border: 1px solid var(--theme-popup-border); +} +.label-doc-folding:hover { + background-color: #e6e6e6; +} + +.lint-doc-md > h3 { + border-top: 1px solid var(--theme-popup-border); + padding: 10px 15px; + margin: 0 -15px; + font-size: 18px; +} +.lint-doc-md > h3:first-child { + border-top: none; + padding-top: 0px; +} + +@media (max-width:749px) { + .lint-additional-info-container { + display: flex; + flex-flow: column; + } + .lint-additional-info-item + .lint-additional-info-item { + border-top: 1px solid var(--theme-popup-border); + } +} +@media (min-width:750px) { + .lint-additional-info-container { + display: flex; + flex-flow: row; + } + .lint-additional-info-item + .lint-additional-info-item { + border-left: 1px solid var(--theme-popup-border); + } +} + +.lint-additional-info-item { + display: inline-flex; + min-width: 200px; + flex-grow: 1; + padding: 9px 5px 5px 15px; +} + +.label-applicability { + background-color: #777777; + margin: auto 5px; +} + +.label-version { + background-color: #777777; + margin: auto 5px; + font-family: monospace; +} + +details { + border-radius: 4px; + padding: .5em .5em 0; +} + +code { + white-space: pre !important; +} + +summary { + font-weight: bold; + margin: -.5em -.5em 0; + padding: .5em; + display: revert; +} + +details[open] { + padding: .5em; +} + +/* Expanding the mdBook theme*/ +.light { + --inline-code-bg: #f6f7f6; +} +.rust { + --inline-code-bg: #f6f7f6; +} +.coal { + --inline-code-bg: #1d1f21; +} +.navy { + --inline-code-bg: #1d1f21; +} +.ayu { + --inline-code-bg: #191f26; +} + +.theme-dropdown { + position: absolute; + margin: 0.7em; + z-index: 10; +} + +/* Applying the mdBook theme */ +.theme-icon { + text-align: center; + width: 2em; + height: 2em; + line-height: 2em; + border: solid 1px var(--icons); + border-radius: 5px; + user-select: none; + cursor: pointer; +} +.theme-icon:hover { + background: var(--theme-hover); +} +.theme-choice { + display: none; + list-style: none; + border: 1px solid var(--theme-popup-border); + border-radius: 5px; + color: var(--fg); + background: var(--theme-popup-bg); + padding: 0 0; + overflow: hidden; +} + +.theme-dropdown.open .theme-choice { + display: block; +} + +.theme-choice > li { + padding: 5px 10px; + font-size: 0.8em; + user-select: none; + cursor: pointer; +} + +.theme-choice > li:hover { + background: var(--theme-hover); +} + +.alert { + color: var(--fg); + background: var(--theme-hover); + border: 1px solid var(--theme-popup-border); +} +.page-header { + border-color: var(--theme-popup-border); +} +.panel-default > .panel-heading { + background: var(--theme-hover); + color: var(--fg); + border: 1px solid var(--theme-popup-border); +} +.panel-default > .panel-heading:hover { + filter: brightness(90%); +} +.list-group-item { + background: 0%; + border: 1px solid var(--theme-popup-border); +} +.panel, pre, hr { + background: var(--bg); + border: 1px solid var(--theme-popup-border); +} + +#version-filter-selector .checkbox { + display: flex; +} + +#version-filter { + min-width: available; +} + +#version-filter li label { + padding-right: 0; + width: 35%; +} + +.version-filter-input { + height: 60%; + width: 30%; + text-align: center; + border: none; + border-bottom: 1px solid #000000; +} + +#filter-label, .filter-clear { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); + filter: brightness(95%); +} +#filter-label:hover, .filter-clear:hover { + filter: brightness(90%); +} +.filter-input { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); +} + +.filter-input::-webkit-input-placeholder, +.filter-input::-moz-placeholder { + color: var(--searchbar-fg); + opacity: 30%; +} + +.expansion-group { + margin-top: 15px; + padding: 0px 8px; + display: flex; + flex-wrap: nowrap; +} + +@media (min-width: 992px) { + .expansion-group { + margin-top: 0; + padding: 0px 15px; + } +} + +.expansion-control { + width: 50%; +} + +:not(pre) > code { + color: var(--inline-code-color); + background-color: var(--inline-code-bg); +} +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +body { + background: var(--bg); + color: var(--fg); +} From 7c5209121d52411d95bca199124d35ab3300ce9d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 30 Jul 2024 17:31:42 +0200 Subject: [PATCH 029/685] Add new settings menu --- util/gh-pages/index.html | 27 ++++++++++++++++---- util/gh-pages/script.js | 45 ++++++++++++++++++++++++++++++---- util/gh-pages/style.css | 53 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 267354cc8bfc6..12c9608f6f5f7 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -26,11 +26,28 @@ -
-
🖌
-
    -
  • {{name}}
  • -
+
+
+ +
+
+ +
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index ed1e090e1b540..f072327bc340f 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -68,6 +68,24 @@ } } }) + .directive('settingsDropdown', function ($document) { + return { + restrict: 'A', + link: function ($scope, $element, $attr) { + $element.bind('click', function () { + $element.toggleClass('open'); + $element.addClass('open-recent'); + }); + + $document.bind('click', function () { + if (!$element.hasClass('open-recent')) { + $element.removeClass('open'); + } + $element.removeClass('open-recent'); + }) + } + } + }) .directive('filterDropdown', function ($document) { return { restrict: 'A', @@ -537,6 +555,16 @@ function getQueryVariable(variable) { } } +function storeValue(settingName, value) { + try { + localStorage.setItem(`clippy-lint-list-${settingName}`, value); + } catch (e) { } +} + +function loadValue(settingName) { + return localStorage.getItem(`clippy-lint-list-${settingName}`); +} + function setTheme(theme, store) { let enableHighlight = false; let enableNight = false; @@ -569,14 +597,12 @@ function setTheme(theme, store) { document.getElementById("styleAyu").disabled = !enableAyu; if (store) { - try { - localStorage.setItem('clippy-lint-list-theme', theme); - } catch (e) { } + storeValue("theme", theme); } } function handleShortcut(ev) { - if (ev.ctrlKey || ev.altKey || ev.metaKey) { + if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { return; } @@ -601,11 +627,20 @@ function handleShortcut(ev) { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); +function changeSetting(elem) { + if (elem.id === "disable-shortcuts") { + disableShortcuts = elem.checked; + storeValue(elem.id, elem.checked); + } +} + // loading the theme after the initial load const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); -const theme = localStorage.getItem('clippy-lint-list-theme'); +const theme = loadValue('theme'); if (prefersDark.matches && !theme) { setTheme("coal", false); } else { setTheme(theme, false); } +let disableShortcuts = loadValue('disable-shortcuts') === "true"; +document.getElementById("disable-shortcuts").checked = disableShortcuts; diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index f9feb0ba13ae3..4ad8b502dd82f 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -220,14 +220,20 @@ details[open] { --inline-code-bg: #191f26; } -.theme-dropdown { +#settings { position: absolute; margin: 0.7em; z-index: 10; + display: flex; +} + +.menu-container { + position: relative; + width: 28px; } /* Applying the mdBook theme */ -.theme-icon { +.theme-icon, .settings-icon { text-align: center; width: 2em; height: 2em; @@ -237,10 +243,10 @@ details[open] { user-select: none; cursor: pointer; } -.theme-icon:hover { +.theme-icon:hover, .settings-icon:hover { background: var(--theme-hover); } -.theme-choice { +.theme-choice, .settings-choice { display: none; list-style: none; border: 1px solid var(--theme-popup-border); @@ -249,9 +255,46 @@ details[open] { background: var(--theme-popup-bg); padding: 0 0; overflow: hidden; + position: absolute; +} + +.settings-dropdown { + margin-left: 4px; +} + +.settings-icon::before { + /* Wheel */ + content: url('data:image/svg+xml,\ +'); + width: 18px; + height: 18px; + display: block; + filter: invert(0.7); + padding-left: 4px; + padding-top: 3px; +} + +.settings-choice { + padding: 4px; + width: 212px; +} + +.settings-choice label { + cursor: pointer; } -.theme-dropdown.open .theme-choice { +.theme-dropdown.open .theme-choice, .settings-dropdown.open .settings-choice { display: block; } From 8bf5a8332244dfc57cbd608ece30929ff44b7316 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Tue, 30 Jul 2024 22:34:56 +0500 Subject: [PATCH 030/685] Fix example --- clippy_lints/src/unused_io_amount.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 448946bd66d51..cfc4ea46bdb2b 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -34,11 +34,18 @@ declare_clippy_lint! { /// ```rust,ignore /// use std::io; /// fn foo(w: &mut W) -> io::Result<()> { - /// // must be `w.write_all(b"foo")?;` /// w.write(b"foo")?; /// Ok(()) /// } /// ``` + /// Use instead: + /// ```rust,ignore + /// use std::io; + /// fn foo(w: &mut W) -> io::Result<()> { + /// w.write_all(b"foo")?; + /// Ok(()) + /// } + /// ``` #[clippy::version = "pre 1.29.0"] pub UNUSED_IO_AMOUNT, correctness, From e65a48efd9b711281c70667c5392de70a9e1f7c3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 11:59:47 -0700 Subject: [PATCH 031/685] Document WebAssembly target feature expectations This commit is a result of the discussion on #128475 and incorporates parts of #109807 as well. This is all done as a new page of documentation for the `wasm32-unknown-unknown` target which previously did not exist. This new page goes into details about the preexisting target and additionally documents the expectations for WebAssembly features and code generation. The tl;dr is that LLVM will enable features over time after most engines have had support for awhile. Compiling without features requires `-Ctarget-cpu=mvp` to rustc plus `-Zbuild-std` to Cargo. Closes #109807 Closes #128475 --- src/doc/rustc/src/platform-support.md | 2 +- .../wasm32-unknown-unknown.md | 154 ++++++++++++++++++ .../src/platform-support/wasm32-wasip1.md | 7 + .../src/platform-support/wasm32-wasip2.md | 7 + 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index cbb338f481172..1fb7d2d150385 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -190,7 +190,7 @@ target | std | notes [`thumbv8m.main-none-eabi`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline [`thumbv8m.main-none-eabihf`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline, hardfloat `wasm32-unknown-emscripten` | ✓ | WebAssembly via Emscripten -`wasm32-unknown-unknown` | ✓ | WebAssembly +[`wasm32-unknown-unknown`](platform-support/wasm32-unknown-unknown.md) | ✓ | WebAssembly `wasm32-wasi` | ✓ | WebAssembly with WASI (undergoing a [rename to `wasm32-wasip1`][wasi-rename]) [`wasm32-wasip1`](platform-support/wasm32-wasip1.md) | ✓ | WebAssembly with WASI [`wasm32-wasip1-threads`](platform-support/wasm32-wasip1-threads.md) | ✓ | WebAssembly with WASI Preview 1 and threads diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md new file mode 100644 index 0000000000000..03126eaa50cbb --- /dev/null +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -0,0 +1,154 @@ +# `wasm32-unknown-unknown` + +**Tier: 2** + +The `wasm32-unknown-unknown` target is a WebAssembly compilation target which +does not import any functions from the host for the standard library. This is +the "minimal" WebAssembly in the sense of making the fewest assumptions about +the host environment. This target is often used when compiling to the web or +JavaScript environments as there is not standard for what functions can be +imported on the web. This target can also be useful for creating minimal or +bare-bones WebAssembly binaries. + +The `wasm32-unknown-unknown` target has support for the Rust standard library +but many parts of the standard library do not work and return errors. For +example `println!` does nothing, `std::fs` always return errors, and +`std::thread::spawn` will panic. There is no means by which this can be +overridden. For a WebAssembly target that more fully supports the standard +library see the [`wasm32-wasip1`](./wasm32-wasip1.md) or +[`wasm32-wasip2`](./wasm32-wasip2.md) targets. + +The `wasm32-unknown-unknown` target has full support for the `core` and `alloc` +crates. It additionally supports the `HashMap` type in the `std` crate, although +hash maps are not randomized like they are on other platforms. + +One existing user of this target (please feel free to edit and expand this list +too) is the [`wasm-bindgen` project](https://github.com/rustwasm/wasm-bindgen) +which facilitates Rust code interoperating with JavaScript code. Note, though, +that not all uses of `wasm32-unknown-unknown` are using JavaScript and the web. + +## Target maintainers + +When this target was added to the compiler platform-specific documentation here +was not maintained at that time. This means that the list below is not +exhaustive and there are more interested parties in this target. That being +said since when this document was last updated those interested in maintaining +this target are: + +- Alex Crichton, https://github.com/alexcrichton + +## Requirements + +This target is cross-compiled. The target includes support for `std` itself, +but as mentioned above many pieces of functionality that require an operating +system do not work and will return errors. + +This target currently has no equivalent in C/C++. There is no C/C++ toolchain +for this target. While interop is theoretically possible it's recommended to +instead use one of: + +* `wasm32-unknown-emscripten` - for web-based use cases the Emscripten + toolchain is typically chosen for running C/C++. +* [`wasm32-wasip1`](./wasm32-wasip1.md) - the wasi-sdk toolchain is used to + compile C/C++ on this target and can interop with Rust code. WASI works on + the web so far as there's no blocker, but an implementation of WASI APIs + must be either chosen or reimplemented. + +This target has no build requirements beyond what's in-tree in the Rust +repository. Linking binaries requires LLD to be enabled for the `wasm-ld` +driver. This target uses the `dlmalloc` crate as the default global allocator. + +## Building the target + +Building this target can be done by: + +* Configure the `wasm32-unknown-unknown` target to get built. +* Configure LLD to be built. +* Ensure the `WebAssembly` target backend is not disabled in LLVM. + +These are all controlled through `config.toml` options. It should be possible +to build this target on any platform. + +## Building Rust programs + +Rust programs can be compiled by adding this target via rustup: + +```sh +$ rustup target add wasm32-unknown-unknown +``` + +and then compiling with the target: + +```sh +$ rustc foo.rs --target wasm32-unknown-unknown +$ file foo.wasm +``` + +## Cross-compilation + +This target can be cross-compiled from any hosts. + +## Testing + +This target is not tested in CI for the rust-lang/rust repository. Many tests +must be disabled to run on this target and failures are non-obvious because +println doesn't work in the standard library. It's recommended to test the +`wasm32-wasip1` target instead for WebAssembly compatibility. + +## Conditionally compiling code + +It's recommended to conditionally compile code for this target with: + +```text +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +``` + +Note that there is no way to tell via `#[cfg]` whether code will be running on +the web or not. + +## Enabled WebAssembly features + +WebAssembly is an evolving standard which adds new features such as new +instructions over time. This target's default set of supported WebAssembly +features will additionally change over time. The `wasm32-unknown-unknown` target +inherits the default settings of LLVM which typically matches the default +settings of Emscripten as well. + +Changes to WebAssembly go through a [proposals process][proposals] but reaching +the final stage (stage 5) does not automatically mean that the feature will be +enabled in LLVM and Rust by default. At this time the general guidance is that +features must be present in most engines for a "good chunk of time" before +they're enabled in LLVM by default. There is currently not exact number of +months or engines that are required to enable features by default. + +[proposals]: https://github.com/WebAssembly/proposals + +If you're compiling WebAssembly code for an engine that does not support a +feature in LLVM's default feature set then the feature must be disabled at +compile time. Note, though, that enabled features may be used in the standard +library or precompiled libraries shipped via rustup. This means that not only +does your own code need to be compiled with the correct set of flags but the +Rust standard library additionally must be recompiled. + +Compiling all code for the initial release of WebAssembly looks like: + +```sh +$ export RUSTFLAG=-Ctarget-cpu=mvp +$ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown +``` + +Here the `mvp` "cpu" is a placeholder in LLVM for disabling all supported +features by default. Cargo's `-Zbuild-std` feature, a Nightly Rust feature, is +then used to recompile the standard library in addition to your own code. This +will produce a binary that uses only the original WebAssembly features by +default and no proposals since its inception. + +To enable individual features it can be done with `-Ctarget-feature=+foo`. +Available features can be found through: + +```sh +$ rustc -Ctarget-feature=help --target wasm32-unknown-unknown +``` + +You'll need to consult your WebAssembly engine's documentation to learn more +about the supported WebAssembly features the engine has. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1.md b/src/doc/rustc/src/platform-support/wasm32-wasip1.md index fb70bbdc2b403..7a7cac9aeeb1e 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1.md @@ -132,3 +132,10 @@ It's recommended to conditionally compile code for this target with: Note that the `target_env = "p1"` condition first appeared in Rust 1.80. Prior to Rust 1.80 the `target_env` condition was not set. + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is currently the +same across all WebAssembly targets. For more information on WebAssembly +features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip2.md b/src/doc/rustc/src/platform-support/wasm32-wasip2.md index 1e53fbc178e2e..e4ef65bcfec55 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip2.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip2.md @@ -61,3 +61,10 @@ It's recommended to conditionally compile code for this target with: ```text #[cfg(all(target_os = "wasi", target_env = "p2"))] ``` + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is currently the +same across all WebAssembly targets. For more information on WebAssembly +features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) From dea3846edc94ac2b840458594b2772f0c960e329 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 12:16:42 -0700 Subject: [PATCH 032/685] Review comments --- .../src/platform-support/wasm32-unknown-unknown.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 03126eaa50cbb..85915cf90a9c3 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -6,7 +6,7 @@ The `wasm32-unknown-unknown` target is a WebAssembly compilation target which does not import any functions from the host for the standard library. This is the "minimal" WebAssembly in the sense of making the fewest assumptions about the host environment. This target is often used when compiling to the web or -JavaScript environments as there is not standard for what functions can be +JavaScript environments as there is no standard for what functions can be imported on the web. This target can also be useful for creating minimal or bare-bones WebAssembly binaries. @@ -86,13 +86,13 @@ $ file foo.wasm ## Cross-compilation -This target can be cross-compiled from any hosts. +This target can be cross-compiled from any host. ## Testing This target is not tested in CI for the rust-lang/rust repository. Many tests must be disabled to run on this target and failures are non-obvious because -println doesn't work in the standard library. It's recommended to test the +`println!` doesn't work in the standard library. It's recommended to test the `wasm32-wasip1` target instead for WebAssembly compatibility. ## Conditionally compiling code @@ -118,7 +118,7 @@ Changes to WebAssembly go through a [proposals process][proposals] but reaching the final stage (stage 5) does not automatically mean that the feature will be enabled in LLVM and Rust by default. At this time the general guidance is that features must be present in most engines for a "good chunk of time" before -they're enabled in LLVM by default. There is currently not exact number of +they're enabled in LLVM by default. There is currently no exact number of months or engines that are required to enable features by default. [proposals]: https://github.com/WebAssembly/proposals @@ -144,7 +144,8 @@ will produce a binary that uses only the original WebAssembly features by default and no proposals since its inception. To enable individual features it can be done with `-Ctarget-feature=+foo`. -Available features can be found through: +Available features for Rust code itself are documented in the [reference] and +can also be found through: ```sh $ rustc -Ctarget-feature=help --target wasm32-unknown-unknown @@ -152,3 +153,5 @@ $ rustc -Ctarget-feature=help --target wasm32-unknown-unknown You'll need to consult your WebAssembly engine's documentation to learn more about the supported WebAssembly features the engine has. + +[reference]: https://doc.rust-lang.org/reference/attributes/codegen.html#wasm32-or-wasm64 From 927633cae4eec359901523d8fa9a962749c89560 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 12:20:44 -0700 Subject: [PATCH 033/685] Add a note about libraries and `#[target_feature]` --- .../wasm32-unknown-unknown.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 85915cf90a9c3..2bd50f6e6db8c 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -155,3 +155,36 @@ You'll need to consult your WebAssembly engine's documentation to learn more about the supported WebAssembly features the engine has. [reference]: https://doc.rust-lang.org/reference/attributes/codegen.html#wasm32-or-wasm64 + +Note that it is still possible for Rust crates and libraries to enable +WebAssembly features on a per-function level. This means that the build +command above may not be sufficent to disable all WebAssembly features. If the +final binary still has SIMD instructions, for example, the function in question +will need to be found and the crate in question will likely contain something +like: + +```rust +#[target_feature(enable = "simd128")] +fn foo() { + // ... +} +``` + +In this situation there is no compiler flag to disable emission of SIMD +instructions and the crate must instead be modified to not include this function +at compile time either by default or through a Cargo feature. For crate authors +it's recommended to avoid `#[target_feature(enable = "...")]` except where +necessary and instead use: + +```rust +#[cfg(target_feature = "simd128")] +fn foo() { + // ... +} +``` + +That is to say instead of enabling target features it's recommended to +conditionally compile code instead. This is notably different to the way native +platforms such as x86\_64 work, and this is due to the fact that WebAssembly +binaries must only contain code the engine understands. Native binaries work so +long as the CPU doesn't execute unknown code dynamically at runtime. From cfe3ea65d29dbed12cf5466b51dbeb094a3729f1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 13:13:19 -0700 Subject: [PATCH 034/685] Add new page to SUMMARY.md --- src/doc/rustc/src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 08460af15d4c7..c5622a5e14e20 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -78,6 +78,7 @@ - [wasm32-wasip1](platform-support/wasm32-wasip1.md) - [wasm32-wasip1-threads](platform-support/wasm32-wasip1-threads.md) - [wasm32-wasip2](platform-support/wasm32-wasip2.md) + - [wasm32-unknown-unknown](platform-support/wasm32-unknown-unknown.md) - [wasm64-unknown-unknown](platform-support/wasm64-unknown-unknown.md) - [\*-win7-windows-msvc](platform-support/win7-windows-msvc.md) - [x86_64-fortanix-unknown-sgx](platform-support/x86_64-fortanix-unknown-sgx.md) From ce7f1b77f400bbf42e2ca9628dec087c1a2605c5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 14:33:04 -0700 Subject: [PATCH 035/685] Ignore two new doc blocks in testing --- src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 2bd50f6e6db8c..a4b547bd1fff1 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -163,7 +163,7 @@ final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: -```rust +```rust,ignore #[target_feature(enable = "simd128")] fn foo() { // ... @@ -176,7 +176,7 @@ at compile time either by default or through a Cargo feature. For crate authors it's recommended to avoid `#[target_feature(enable = "...")]` except where necessary and instead use: -```rust +```rust,ignore #[cfg(target_feature = "simd128")] fn foo() { // ... From b6f65a4b224ec74918ecc5e02527c72e75a18f89 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 14:36:55 -0700 Subject: [PATCH 036/685] Document on-by-default features --- .../rustc/src/platform-support/wasm32-unknown-unknown.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index a4b547bd1fff1..5de80fea6bf76 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -123,6 +123,14 @@ months or engines that are required to enable features by default. [proposals]: https://github.com/WebAssembly/proposals +As of the time of this writing the proposals that are enabled by default (the +`generic` CPU in LLVM terminology) are: + +* `multivalue` +* `mutable-globals` +* `reference-types` +* `sign-ext` + If you're compiling WebAssembly code for an engine that does not support a feature in LLVM's default feature set then the feature must be disabled at compile time. Note, though, that enabled features may be used in the standard From a5082ef5a8de1296d5d623b4a76711f2e3176e26 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 15:02:32 -0700 Subject: [PATCH 037/685] Appease tidy --- src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 5de80fea6bf76..1c2894e3a7390 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -171,7 +171,7 @@ final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: -```rust,ignore +```rust,ignore (not-always-compiled-to-wasm) #[target_feature(enable = "simd128")] fn foo() { // ... @@ -184,7 +184,7 @@ at compile time either by default or through a Cargo feature. For crate authors it's recommended to avoid `#[target_feature(enable = "...")]` except where necessary and instead use: -```rust,ignore +```rust,ignore (not-always-compiled-to-wasm) #[cfg(target_feature = "simd128")] fn foo() { // ... From b1493ba5194fcc5cfe4f6315db288e4e18509110 Mon Sep 17 00:00:00 2001 From: beetrees Date: Sat, 1 Jun 2024 12:25:16 +0100 Subject: [PATCH 038/685] Move ZST ABI handling to `rustc_target` --- compiler/rustc_target/src/abi/call/mod.rs | 13 +++- compiler/rustc_target/src/abi/call/powerpc.rs | 20 +++-- compiler/rustc_target/src/abi/call/s390x.rs | 18 +++-- compiler/rustc_target/src/abi/call/sparc64.rs | 12 ++- .../rustc_target/src/abi/call/x86_win64.rs | 10 ++- compiler/rustc_ty_utils/src/abi.rs | 27 +------ src/tools/compiletest/src/command-list.rs | 5 ++ tests/ui/abi/c-zst.other-linux.stderr | 67 ++++++++++++++++ tests/ui/abi/c-zst.other.stderr | 67 ++++++++++++++++ tests/ui/abi/c-zst.powerpc-linux.stderr | 78 +++++++++++++++++++ tests/ui/abi/c-zst.rs | 27 +++++++ tests/ui/abi/c-zst.s390x-linux.stderr | 78 +++++++++++++++++++ tests/ui/abi/c-zst.sparc64-linux.stderr | 78 +++++++++++++++++++ .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 78 +++++++++++++++++++ tests/ui/abi/sysv64-zst.rs | 8 ++ tests/ui/abi/sysv64-zst.stderr | 67 ++++++++++++++++ tests/ui/abi/win64-zst.other.stderr | 67 ++++++++++++++++ tests/ui/abi/win64-zst.rs | 11 +++ tests/ui/abi/win64-zst.windows-gnu.stderr | 78 +++++++++++++++++++ 19 files changed, 766 insertions(+), 43 deletions(-) create mode 100644 tests/ui/abi/c-zst.other-linux.stderr create mode 100644 tests/ui/abi/c-zst.other.stderr create mode 100644 tests/ui/abi/c-zst.powerpc-linux.stderr create mode 100644 tests/ui/abi/c-zst.rs create mode 100644 tests/ui/abi/c-zst.s390x-linux.stderr create mode 100644 tests/ui/abi/c-zst.sparc64-linux.stderr create mode 100644 tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr create mode 100644 tests/ui/abi/sysv64-zst.rs create mode 100644 tests/ui/abi/sysv64-zst.stderr create mode 100644 tests/ui/abi/win64-zst.other.stderr create mode 100644 tests/ui/abi/win64-zst.rs create mode 100644 tests/ui/abi/win64-zst.windows-gnu.stderr diff --git a/compiler/rustc_target/src/abi/call/mod.rs b/compiler/rustc_target/src/abi/call/mod.rs index 5bfc528dffc83..25e4d70945b2c 100644 --- a/compiler/rustc_target/src/abi/call/mod.rs +++ b/compiler/rustc_target/src/abi/call/mod.rs @@ -642,7 +642,7 @@ impl<'a, Ty> ArgAbi<'a, Ty> { pub fn make_indirect(&mut self) { match self.mode { PassMode::Direct(_) | PassMode::Pair(_, _) => { - self.mode = Self::indirect_pass_mode(&self.layout); + self.make_indirect_force(); } PassMode::Indirect { attrs: _, meta_attrs: _, on_stack: false } => { // already indirect @@ -652,6 +652,11 @@ impl<'a, Ty> ArgAbi<'a, Ty> { } } + /// Same as make_indirect, but doesn't check the current `PassMode`. + pub fn make_indirect_force(&mut self) { + self.mode = Self::indirect_pass_mode(&self.layout); + } + /// Pass this argument indirectly, by placing it at a fixed stack offset. /// This corresponds to the `byval` LLVM argument attribute. /// This is only valid for sized arguments. @@ -871,10 +876,10 @@ impl<'a, Ty> FnAbi<'a, Ty> { } "x86_64" => match abi { spec::abi::Abi::SysV64 { .. } => x86_64::compute_abi_info(cx, self), - spec::abi::Abi::Win64 { .. } => x86_win64::compute_abi_info(self), + spec::abi::Abi::Win64 { .. } => x86_win64::compute_abi_info(cx, self), _ => { if cx.target_spec().is_like_windows { - x86_win64::compute_abi_info(self) + x86_win64::compute_abi_info(cx, self) } else { x86_64::compute_abi_info(cx, self) } @@ -898,7 +903,7 @@ impl<'a, Ty> FnAbi<'a, Ty> { "csky" => csky::compute_abi_info(self), "mips" | "mips32r6" => mips::compute_abi_info(cx, self), "mips64" | "mips64r6" => mips64::compute_abi_info(cx, self), - "powerpc" => powerpc::compute_abi_info(self), + "powerpc" => powerpc::compute_abi_info(cx, self), "powerpc64" => powerpc64::compute_abi_info(cx, self), "s390x" => s390x::compute_abi_info(cx, self), "msp430" => msp430::compute_abi_info(self), diff --git a/compiler/rustc_target/src/abi/call/powerpc.rs b/compiler/rustc_target/src/abi/call/powerpc.rs index 70c32db0a871b..cb80d64c94304 100644 --- a/compiler/rustc_target/src/abi/call/powerpc.rs +++ b/compiler/rustc_target/src/abi/call/powerpc.rs @@ -1,4 +1,5 @@ use crate::abi::call::{ArgAbi, FnAbi}; +use crate::spec::HasTargetSpec; fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { if ret.layout.is_aggregate() { @@ -8,7 +9,17 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'_, Ty>) { + if arg.is_ignore() { + // powerpc-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs. + if cx.target_spec().os == "linux" + && matches!(&*cx.target_spec().env, "gnu" | "musl" | "uclibc") + && arg.layout.is_zst() + { + arg.make_indirect_force(); + } + return; + } if arg.layout.is_aggregate() { arg.make_indirect(); } else { @@ -16,15 +27,12 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub fn compute_abi_info(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) { if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } for arg in fn_abi.args.iter_mut() { - if arg.is_ignore() { - continue; - } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/abi/call/s390x.rs b/compiler/rustc_target/src/abi/call/s390x.rs index 1a2191082d5de..7dcbb3e4a9e9b 100644 --- a/compiler/rustc_target/src/abi/call/s390x.rs +++ b/compiler/rustc_target/src/abi/call/s390x.rs @@ -3,6 +3,7 @@ use crate::abi::call::{ArgAbi, FnAbi, Reg}; use crate::abi::{HasDataLayout, TyAbiInterface}; +use crate::spec::HasTargetSpec; fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { if !ret.layout.is_aggregate() && ret.layout.size.bits() <= 64 { @@ -15,12 +16,22 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, - C: HasDataLayout, + C: HasDataLayout + HasTargetSpec, { if !arg.layout.is_sized() { // Not touching this... return; } + if arg.is_ignore() { + // s390x-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs. + if cx.target_spec().os == "linux" + && matches!(&*cx.target_spec().env, "gnu" | "musl" | "uclibc") + && arg.layout.is_zst() + { + arg.make_indirect_force(); + } + return; + } if !arg.layout.is_aggregate() && arg.layout.size.bits() <= 64 { arg.extend_integer_width_to(64); return; @@ -46,16 +57,13 @@ where pub fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, - C: HasDataLayout, + C: HasDataLayout + HasTargetSpec, { if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } for arg in fn_abi.args.iter_mut() { - if arg.is_ignore() { - continue; - } classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/abi/call/sparc64.rs b/compiler/rustc_target/src/abi/call/sparc64.rs index c0952130e0410..3b2bf9b3187f1 100644 --- a/compiler/rustc_target/src/abi/call/sparc64.rs +++ b/compiler/rustc_target/src/abi/call/sparc64.rs @@ -4,6 +4,7 @@ use crate::abi::call::{ ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, FnAbi, Reg, Uniform, }; use crate::abi::{self, HasDataLayout, Scalar, Size, TyAbiInterface, TyAndLayout}; +use crate::spec::HasTargetSpec; #[derive(Clone, Debug)] pub struct Sdata { @@ -211,7 +212,7 @@ where pub fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, - C: HasDataLayout, + C: HasDataLayout + HasTargetSpec, { if !fn_abi.ret.is_ignore() { classify_arg(cx, &mut fn_abi.ret, Size::from_bytes(32)); @@ -219,7 +220,14 @@ where for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { - continue; + // sparc64-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs. + if cx.target_spec().os == "linux" + && matches!(&*cx.target_spec().env, "gnu" | "musl" | "uclibc") + && arg.layout.is_zst() + { + arg.make_indirect_force(); + } + return; } classify_arg(cx, arg, Size::from_bytes(16)); } diff --git a/compiler/rustc_target/src/abi/call/x86_win64.rs b/compiler/rustc_target/src/abi/call/x86_win64.rs index 4e19460bd28c2..6ca01cf84eaa4 100644 --- a/compiler/rustc_target/src/abi/call/x86_win64.rs +++ b/compiler/rustc_target/src/abi/call/x86_win64.rs @@ -1,9 +1,10 @@ use crate::abi::call::{ArgAbi, FnAbi, Reg}; use crate::abi::{Abi, Float, Primitive}; +use crate::spec::HasTargetSpec; // Win64 ABI: https://docs.microsoft.com/en-us/cpp/build/parameter-passing -pub fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub fn compute_abi_info(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) { let fixup = |a: &mut ArgAbi<'_, Ty>| { match a.layout.abi { Abi::Uninhabited | Abi::Aggregate { sized: false } => {} @@ -37,6 +38,13 @@ pub fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { } for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { + // x86_64-pc-windows-gnu doesn't ignore ZSTs. + if cx.target_spec().os == "windows" + && cx.target_spec().env == "gnu" + && arg.layout.is_zst() + { + arg.make_indirect_force(); + } continue; } fixup(arg); diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index f1675f80717f0..464d03e4b35f2 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -584,7 +584,7 @@ fn fn_abi_new_uncached<'tcx>( let conv = conv_from_spec_abi(cx.tcx(), sig.abi, sig.c_variadic); let mut inputs = sig.inputs(); - let extra_args = if sig.abi == RustCall { + let extra_args = if sig.abi == SpecAbi::RustCall { assert!(!sig.c_variadic && extra_args.is_empty()); if let Some(input) = sig.inputs().last() { @@ -608,18 +608,6 @@ fn fn_abi_new_uncached<'tcx>( extra_args }; - let target = &cx.tcx.sess.target; - let target_env_gnu_like = matches!(&target.env[..], "gnu" | "musl" | "uclibc"); - let win_x64_gnu = target.os == "windows" && target.arch == "x86_64" && target.env == "gnu"; - let linux_s390x_gnu_like = - target.os == "linux" && target.arch == "s390x" && target_env_gnu_like; - let linux_sparc64_gnu_like = - target.os == "linux" && target.arch == "sparc64" && target_env_gnu_like; - let linux_powerpc_gnu_like = - target.os == "linux" && target.arch == "powerpc" && target_env_gnu_like; - use SpecAbi::*; - let rust_abi = matches!(sig.abi, RustIntrinsic | Rust | RustCall); - let is_drop_in_place = fn_def_id.is_some() && fn_def_id == cx.tcx.lang_items().drop_in_place_fn(); @@ -659,18 +647,7 @@ fn fn_abi_new_uncached<'tcx>( }); if arg.layout.is_zst() { - // For some forsaken reason, x86_64-pc-windows-gnu - // doesn't ignore zero-sized struct arguments. - // The same is true for {s390x,sparc64,powerpc}-unknown-linux-{gnu,musl,uclibc}. - if is_return - || rust_abi - || (!win_x64_gnu - && !linux_s390x_gnu_like - && !linux_sparc64_gnu_like - && !linux_powerpc_gnu_like) - { - arg.mode = PassMode::Ignore; - } + arg.mode = PassMode::Ignore; } Ok(arg) diff --git a/src/tools/compiletest/src/command-list.rs b/src/tools/compiletest/src/command-list.rs index 288f90ea12399..a405cd486c713 100644 --- a/src/tools/compiletest/src/command-list.rs +++ b/src/tools/compiletest/src/command-list.rs @@ -91,10 +91,12 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-nvptx64-nvidia-cuda", "ignore-openbsd", "ignore-pass", + "ignore-powerpc", "ignore-remote", "ignore-riscv64", "ignore-s390x", "ignore-sgx", + "ignore-sparc64", "ignore-spirv", "ignore-stable", "ignore-stage1", @@ -122,6 +124,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-x86", "ignore-x86_64", "ignore-x86_64-apple-darwin", + "ignore-x86_64-pc-windows-gnu", "ignore-x86_64-unknown-linux-gnu", "incremental", "known-bug", @@ -189,7 +192,9 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "only-msvc", "only-nightly", "only-nvptx64", + "only-powerpc", "only-riscv64", + "only-s390x", "only-sparc", "only-sparc64", "only-stable", diff --git a/tests/ui/abi/c-zst.other-linux.stderr b/tests/ui/abi/c-zst.other-linux.stderr new file mode 100644 index 0000000000000..5a656e6ea66e0 --- /dev/null +++ b/tests/ui/abi/c-zst.other-linux.stderr @@ -0,0 +1,67 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.other.stderr b/tests/ui/abi/c-zst.other.stderr new file mode 100644 index 0000000000000..5a656e6ea66e0 --- /dev/null +++ b/tests/ui/abi/c-zst.other.stderr @@ -0,0 +1,67 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr new file mode 100644 index 0000000000000..ba9738050d87d --- /dev/null +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -0,0 +1,78 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: NoAlias | NoCapture | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.rs b/tests/ui/abi/c-zst.rs new file mode 100644 index 0000000000000..0cfd653b37e87 --- /dev/null +++ b/tests/ui/abi/c-zst.rs @@ -0,0 +1,27 @@ +//@ revisions: other other-linux x86_64-pc-windows-gnu s390x-linux sparc64-linux powerpc-linux +//@ normalize-stderr-test: "(abi|pref|unadjusted_abi_align): Align\([1-8] bytes\)" -> "$1: $$SOME_ALIGN" +// ZSTs are only not ignored when the target_env is "gnu", "musl" or "uclibc". However, Rust does +// not currently support any other target_env on these architectures. + +// Ignore the ZST revisions +//@[other] ignore-x86_64-pc-windows-gnu +//@[other] ignore-linux +//@[other-linux] only-linux +//@[other-linux] ignore-s390x +//@[other-linux] ignore-sparc64 +//@[other-linux] ignore-powerpc + +// Pass the ZST indirectly revisions +//@[x86_64-pc-windows-gnu] only-x86_64-pc-windows-gnu +//@[s390x-linux] only-s390x +//@[s390x-linux] only-linux +//@[sparc64-linux] only-sparc64 +//@[sparc64-linux] only-linux +//@[powerpc-linux] only-powerpc +//@[powerpc-linux] only-linux + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[rustc_abi(debug)] +extern "C" fn pass_zst(_: ()) {} //~ ERROR: fn_abi diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr new file mode 100644 index 0000000000000..ba9738050d87d --- /dev/null +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -0,0 +1,78 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: NoAlias | NoCapture | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr new file mode 100644 index 0000000000000..ba9738050d87d --- /dev/null +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -0,0 +1,78 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: NoAlias | NoCapture | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr new file mode 100644 index 0000000000000..ba9738050d87d --- /dev/null +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -0,0 +1,78 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: NoAlias | NoCapture | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:27:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/sysv64-zst.rs b/tests/ui/abi/sysv64-zst.rs new file mode 100644 index 0000000000000..6f4497e77a181 --- /dev/null +++ b/tests/ui/abi/sysv64-zst.rs @@ -0,0 +1,8 @@ +//@ only-x86_64 +//@ normalize-stderr-test: "(abi|pref|unadjusted_abi_align): Align\([1-8] bytes\)" -> "$1: $$SOME_ALIGN" + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[rustc_abi(debug)] +extern "sysv64" fn pass_zst(_: ()) {} //~ ERROR: fn_abi diff --git a/tests/ui/abi/sysv64-zst.stderr b/tests/ui/abi/sysv64-zst.stderr new file mode 100644 index 0000000000000..8b0b84dfa0699 --- /dev/null +++ b/tests/ui/abi/sysv64-zst.stderr @@ -0,0 +1,67 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86_64SysV, + can_unwind: false, + } + --> $DIR/sysv64-zst.rs:8:1 + | +LL | extern "sysv64" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/win64-zst.other.stderr b/tests/ui/abi/win64-zst.other.stderr new file mode 100644 index 0000000000000..15db141cb5748 --- /dev/null +++ b/tests/ui/abi/win64-zst.other.stderr @@ -0,0 +1,67 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86_64Win64, + can_unwind: false, + } + --> $DIR/win64-zst.rs:11:1 + | +LL | extern "win64" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/win64-zst.rs b/tests/ui/abi/win64-zst.rs new file mode 100644 index 0000000000000..cae32795e16e7 --- /dev/null +++ b/tests/ui/abi/win64-zst.rs @@ -0,0 +1,11 @@ +//@ only-x86_64 +//@ revisions: other windows-gnu +//@ normalize-stderr-test: "(abi|pref|unadjusted_abi_align): Align\([1-8] bytes\)" -> "$1: $$SOME_ALIGN" +//@[other] ignore-windows-gnu +//@[windows-gnu] only-windows-gnu + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[rustc_abi(debug)] +extern "win64" fn pass_zst(_: ()) {} //~ ERROR: fn_abi diff --git a/tests/ui/abi/win64-zst.windows-gnu.stderr b/tests/ui/abi/win64-zst.windows-gnu.stderr new file mode 100644 index 0000000000000..7773e0aa2b572 --- /dev/null +++ b/tests/ui/abi/win64-zst.windows-gnu.stderr @@ -0,0 +1,78 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: NoAlias | NoCapture | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: $SOME_ALIGN, + pref: $SOME_ALIGN, + }, + abi: Aggregate { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86_64Win64, + can_unwind: false, + } + --> $DIR/win64-zst.rs:11:1 + | +LL | extern "win64" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 06197ef3c111fad0498d30b631a141de1aed2843 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 2 Aug 2024 06:59:45 -0700 Subject: [PATCH 039/685] Review comments --- .../src/platform-support/wasm32-unknown-unknown.md | 4 ++-- .../src/platform-support/wasm32-wasip1-threads.md | 14 ++++++++++++++ .../rustc/src/platform-support/wasm32-wasip1.md | 5 ++--- .../rustc/src/platform-support/wasm32-wasip2.md | 5 ++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 1c2894e3a7390..ee37e5e90cfb4 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -141,7 +141,7 @@ Rust standard library additionally must be recompiled. Compiling all code for the initial release of WebAssembly looks like: ```sh -$ export RUSTFLAG=-Ctarget-cpu=mvp +$ export RUSTFLAGS=-Ctarget-cpu=mvp $ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ``` @@ -166,7 +166,7 @@ about the supported WebAssembly features the engine has. Note that it is still possible for Rust crates and libraries to enable WebAssembly features on a per-function level. This means that the build -command above may not be sufficent to disable all WebAssembly features. If the +command above may not be sufficient to disable all WebAssembly features. If the final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md b/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md index c3eda26ca8ef9..aafb64e6778c2 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md @@ -162,3 +162,17 @@ It's recommended to conditionally compile code for this target with: Prior to Rust 1.80 the `target_env = "p1"` key was not set. Currently the `target_feature = "atomics"` is Nightly-only. Note that the precise `#[cfg]` necessary to detect this target may change as the target becomes more stable. + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is similar to +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) but two more features +are included: + +* `bulk-memory` +* `atomics` + +For more information about features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md), but note that the +`mvp` CPU in LLVM does not support this target as it's required that +`bulk-memory`, `atomics`, and `mutable-globals` are all enabled. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1.md b/src/doc/rustc/src/platform-support/wasm32-wasip1.md index 7a7cac9aeeb1e..103cb38be09b0 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1.md @@ -136,6 +136,5 @@ to Rust 1.80 the `target_env` condition was not set. ## Enabled WebAssembly features The default set of WebAssembly features enabled for compilation is currently the -same across all WebAssembly targets. For more information on WebAssembly -features see the documentation for -[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) +same as [`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md). See the +documentation there for more information. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip2.md b/src/doc/rustc/src/platform-support/wasm32-wasip2.md index e4ef65bcfec55..7d22e9e60d5ad 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip2.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip2.md @@ -65,6 +65,5 @@ It's recommended to conditionally compile code for this target with: ## Enabled WebAssembly features The default set of WebAssembly features enabled for compilation is currently the -same across all WebAssembly targets. For more information on WebAssembly -features see the documentation for -[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) +same as [`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md). See the +documentation there for more information. From 22aa104bce457e7d70b05d7a32cdf09f25ac06b1 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 2 Aug 2024 00:28:57 +0800 Subject: [PATCH 040/685] don't suggest turning crate-level attributes into outer style --- compiler/rustc_ast/src/ast.rs | 11 ++++++ compiler/rustc_parse/src/parser/attr.rs | 38 +++++++++++++------ compiler/rustc_parse/src/parser/stmt.rs | 5 +++ tests/ui/delegation/inner-attr.stderr | 5 --- .../attribute/attr-stmt-expr-attr-bad.stderr | 15 -------- tests/ui/parser/attribute/attr.stderr | 5 --- .../inner-attr-after-doc-comment.stderr | 5 --- tests/ui/parser/inner-attr.stderr | 5 --- .../isgg-invalid-outer-attttr-issue-127930.rs | 10 +++++ ...g-invalid-outer-attttr-issue-127930.stderr | 12 ++++++ tests/ui/parser/issues/issue-30318.fixed | 2 +- tests/ui/parser/issues/issue-30318.rs | 2 +- tests/ui/parser/issues/issue-30318.stderr | 8 ++-- 13 files changed, 70 insertions(+), 53 deletions(-) create mode 100644 tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs create mode 100644 tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index fc1af3fc3dd11..0c81c0f4c93e1 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2898,6 +2898,17 @@ pub struct AttrItem { pub tokens: Option, } +impl AttrItem { + pub fn is_valid_for_outer_style(&self) -> bool { + self.path == sym::cfg_attr + || self.path == sym::cfg + || self.path == sym::forbid + || self.path == sym::warn + || self.path == sym::allow + || self.path == sym::deny + } +} + /// `TraitRef`s appear in impls. /// /// Resolution maps each `TraitRef`'s `ref_id` to its defining trait; that's all diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 12b9414d1f760..7b64a60960b5e 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -67,6 +67,7 @@ impl<'a> Parser<'a> { token::CommentKind::Line => OuterAttributeType::DocComment, token::CommentKind::Block => OuterAttributeType::DocBlockComment, }, + true, ) { err.note(fluent::parse_note); err.span_suggestion_verbose( @@ -130,7 +131,11 @@ impl<'a> Parser<'a> { // Emit error if inner attribute is encountered and forbidden. if style == ast::AttrStyle::Inner { - this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy); + this.error_on_forbidden_inner_attr( + attr_sp, + inner_parse_policy, + item.is_valid_for_outer_style(), + ); } Ok(attr::mk_attr_from_item(&self.psess.attr_id_generator, item, None, style, attr_sp)) @@ -142,6 +147,7 @@ impl<'a> Parser<'a> { err: &mut Diag<'_>, span: Span, attr_type: OuterAttributeType, + suggest_to_outer: bool, ) -> Option { let mut snapshot = self.create_snapshot_for_diagnostic(); let lo = span.lo() @@ -176,16 +182,18 @@ impl<'a> Parser<'a> { // FIXME(#100717) err.arg("item", item.kind.descr()); err.span_label(item.span, fluent::parse_label_does_not_annotate_this); - err.span_suggestion_verbose( - replacement_span, - fluent::parse_sugg_change_inner_to_outer, - match attr_type { - OuterAttributeType::Attribute => "", - OuterAttributeType::DocBlockComment => "*", - OuterAttributeType::DocComment => "/", - }, - rustc_errors::Applicability::MachineApplicable, - ); + if suggest_to_outer { + err.span_suggestion_verbose( + replacement_span, + fluent::parse_sugg_change_inner_to_outer, + match attr_type { + OuterAttributeType::Attribute => "", + OuterAttributeType::DocBlockComment => "*", + OuterAttributeType::DocComment => "/", + }, + rustc_errors::Applicability::MachineApplicable, + ); + } return None; } Err(item_err) => { @@ -196,7 +204,12 @@ impl<'a> Parser<'a> { Some(replacement_span) } - pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy) { + pub(super) fn error_on_forbidden_inner_attr( + &self, + attr_sp: Span, + policy: InnerAttrPolicy, + suggest_to_outer: bool, + ) { if let InnerAttrPolicy::Forbidden(reason) = policy { let mut diag = match reason.as_ref().copied() { Some(InnerAttrForbiddenReason::AfterOuterDocComment { prev_doc_comment_span }) => { @@ -230,6 +243,7 @@ impl<'a> Parser<'a> { &mut diag, attr_sp, OuterAttributeType::Attribute, + suggest_to_outer, ) .is_some() { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 7b0daaa14335f..fe8fbeea4767e 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -439,11 +439,16 @@ impl<'a> Parser<'a> { pub fn parse_block(&mut self) -> PResult<'a, P> { let (attrs, block) = self.parse_inner_attrs_and_block()?; if let [.., last] = &*attrs { + let suggest_to_outer = match &last.kind { + ast::AttrKind::Normal(attr) => attr.item.is_valid_for_outer_style(), + _ => false, + }; self.error_on_forbidden_inner_attr( last.span, super::attr::InnerAttrPolicy::Forbidden(Some( InnerAttrForbiddenReason::InCodeBlock, )), + suggest_to_outer, ); } Ok(block) diff --git a/tests/ui/delegation/inner-attr.stderr b/tests/ui/delegation/inner-attr.stderr index f3b53e331ad88..257ab760ffc1a 100644 --- a/tests/ui/delegation/inner-attr.stderr +++ b/tests/ui/delegation/inner-attr.stderr @@ -8,11 +8,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - reuse a as b { #![rustc_dummy] self } -LL + reuse a as b { #[rustc_dummy] self } - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr index 1ba130e20b578..bd860841b8060 100644 --- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr +++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr @@ -359,11 +359,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo!(); } - | error: an inner attribute is not permitted following an outer attribute --> $DIR/attr-stmt-expr-attr-bad.rs:77:32 @@ -375,11 +370,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo![]; } - | error: an inner attribute is not permitted following an outer attribute --> $DIR/attr-stmt-expr-attr-bad.rs:79:32 @@ -391,11 +381,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo!{}; } - | error[E0586]: inclusive range with no end --> $DIR/attr-stmt-expr-attr-bad.rs:85:35 diff --git a/tests/ui/parser/attribute/attr.stderr b/tests/ui/parser/attribute/attr.stderr index 2e0b16efb6cea..a79a5246c2a9a 100644 --- a/tests/ui/parser/attribute/attr.stderr +++ b/tests/ui/parser/attribute/attr.stderr @@ -7,11 +7,6 @@ LL | fn foo() {} | ----------- the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![lang = "foo"] -LL + #[lang = "foo"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/inner-attr-after-doc-comment.stderr b/tests/ui/parser/inner-attr-after-doc-comment.stderr index 6dbc0fd93fd42..f087c2e4d6540 100644 --- a/tests/ui/parser/inner-attr-after-doc-comment.stderr +++ b/tests/ui/parser/inner-attr-after-doc-comment.stderr @@ -13,11 +13,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![recursion_limit="100"] -LL + #[recursion_limit="100"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/inner-attr.stderr b/tests/ui/parser/inner-attr.stderr index 57ca164fc15a2..18a82ea4c3856 100644 --- a/tests/ui/parser/inner-attr.stderr +++ b/tests/ui/parser/inner-attr.stderr @@ -10,11 +10,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![recursion_limit="100"] -LL + #[recursion_limit="100"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs new file mode 100644 index 0000000000000..26541a89a565a --- /dev/null +++ b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs @@ -0,0 +1,10 @@ +#![allow(dead_code)] +fn foo() {} + +#![feature(iter_array_chunks)] //~ ERROR an inner attribute is not permitted in this context +fn bar() {} + +fn main() { + foo(); + bar(); +} diff --git a/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr new file mode 100644 index 0000000000000..d6daa21e7418d --- /dev/null +++ b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr @@ -0,0 +1,12 @@ +error: an inner attribute is not permitted in this context + --> $DIR/isgg-invalid-outer-attttr-issue-127930.rs:4:1 + | +LL | #![feature(iter_array_chunks)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn bar() {} + | ----------- the inner attribute doesn't annotate this function + | + = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/issues/issue-30318.fixed b/tests/ui/parser/issues/issue-30318.fixed index d1661be519393..d4720834746fc 100644 --- a/tests/ui/parser/issues/issue-30318.fixed +++ b/tests/ui/parser/issues/issue-30318.fixed @@ -6,7 +6,7 @@ fn foo() { } //~^ ERROR expected outer doc comment fn bar() { } //~ NOTE the inner doc comment doesn't annotate this function -#[test] //~ ERROR an inner attribute is not permitted in this context +#[cfg(test)] //~ ERROR an inner attribute is not permitted in this context fn baz() { } //~ NOTE the inner attribute doesn't annotate this function //~^^ NOTE inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually diff --git a/tests/ui/parser/issues/issue-30318.rs b/tests/ui/parser/issues/issue-30318.rs index 6f055cd4f7e95..0555379836ac9 100644 --- a/tests/ui/parser/issues/issue-30318.rs +++ b/tests/ui/parser/issues/issue-30318.rs @@ -6,7 +6,7 @@ fn foo() { } //~^ ERROR expected outer doc comment fn bar() { } //~ NOTE the inner doc comment doesn't annotate this function -#![test] //~ ERROR an inner attribute is not permitted in this context +#![cfg(test)] //~ ERROR an inner attribute is not permitted in this context fn baz() { } //~ NOTE the inner attribute doesn't annotate this function //~^^ NOTE inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually diff --git a/tests/ui/parser/issues/issue-30318.stderr b/tests/ui/parser/issues/issue-30318.stderr index c441a92abad92..56bc200db1d3a 100644 --- a/tests/ui/parser/issues/issue-30318.stderr +++ b/tests/ui/parser/issues/issue-30318.stderr @@ -15,16 +15,16 @@ LL | /// Misplaced comment... error: an inner attribute is not permitted in this context --> $DIR/issue-30318.rs:9:1 | -LL | #![test] - | ^^^^^^^^ +LL | #![cfg(test)] + | ^^^^^^^^^^^^^ LL | fn baz() { } | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files help: to annotate the function, change the attribute from inner to outer style | -LL - #![test] -LL + #[test] +LL - #![cfg(test)] +LL + #[cfg(test)] | error[E0753]: expected outer doc comment From 5364cbea80f0e9b251151f40aa67592b0827759e Mon Sep 17 00:00:00 2001 From: rzvxa Date: Sat, 3 Aug 2024 22:52:22 +0330 Subject: [PATCH 041/685] Respect allow inconsistent_struct_constructor on the type definition --- .../src/inconsistent_struct_constructor.rs | 4 ++++ .../ui/inconsistent_struct_constructor.fixed | 21 +++++++++++++++++++ tests/ui/inconsistent_struct_constructor.rs | 21 +++++++++++++++++++ .../ui/inconsistent_struct_constructor.stderr | 4 ++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index 5b0aadf35c62d..da289225509bf 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_lint_allowed; use clippy_utils::source::snippet; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; @@ -71,6 +72,9 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { && let ty = cx.typeck_results().expr_ty(expr) && let Some(adt_def) = ty.ty_adt_def() && adt_def.is_struct() + && let Some(local_def_id) = adt_def.did().as_local() + && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) + && !is_lint_allowed(cx, INCONSISTENT_STRUCT_CONSTRUCTOR, ty_hir_id) && let Some(variant) = adt_def.variants().iter().next() { let mut def_order_map = FxHashMap::default(); diff --git a/tests/ui/inconsistent_struct_constructor.fixed b/tests/ui/inconsistent_struct_constructor.fixed index 5778f8f526f86..4c324587c96fa 100644 --- a/tests/ui/inconsistent_struct_constructor.fixed +++ b/tests/ui/inconsistent_struct_constructor.fixed @@ -15,6 +15,14 @@ struct Foo { z: i32, } +#[derive(Default)] +#[allow(clippy::inconsistent_struct_constructor)] +struct Bar { + x: i32, + y: i32, + z: i32, +} + mod without_base { use super::Foo; @@ -70,4 +78,17 @@ mod with_base { } } +mod with_allow_ty_def { + use super::Bar; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should NOT lint because `Bar` is defined with `#[allow(clippy::inconsistent_struct_constructor)]` + Bar { y, x, z }; + } +} + fn main() {} diff --git a/tests/ui/inconsistent_struct_constructor.rs b/tests/ui/inconsistent_struct_constructor.rs index 9efaf0689342f..d49f236b9b07b 100644 --- a/tests/ui/inconsistent_struct_constructor.rs +++ b/tests/ui/inconsistent_struct_constructor.rs @@ -15,6 +15,14 @@ struct Foo { z: i32, } +#[derive(Default)] +#[allow(clippy::inconsistent_struct_constructor)] +struct Bar { + x: i32, + y: i32, + z: i32, +} + mod without_base { use super::Foo; @@ -74,4 +82,17 @@ mod with_base { } } +mod with_allow_ty_def { + use super::Bar; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should NOT lint because `Bar` is defined with `#[allow(clippy::inconsistent_struct_constructor)]` + Bar { y, x, z }; + } +} + fn main() {} diff --git a/tests/ui/inconsistent_struct_constructor.stderr b/tests/ui/inconsistent_struct_constructor.stderr index 1192271f911fd..97bb7c789a720 100644 --- a/tests/ui/inconsistent_struct_constructor.stderr +++ b/tests/ui/inconsistent_struct_constructor.stderr @@ -1,5 +1,5 @@ error: struct constructor field order is inconsistent with struct definition field order - --> tests/ui/inconsistent_struct_constructor.rs:28:9 + --> tests/ui/inconsistent_struct_constructor.rs:36:9 | LL | Foo { y, x, z }; | ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }` @@ -8,7 +8,7 @@ LL | Foo { y, x, z }; = help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]` error: struct constructor field order is inconsistent with struct definition field order - --> tests/ui/inconsistent_struct_constructor.rs:55:9 + --> tests/ui/inconsistent_struct_constructor.rs:63:9 | LL | / Foo { LL | | z, From 78caecf8f30dbdbfcb6e0fda25edc72b3e4d04a5 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sat, 3 Aug 2024 21:04:19 -0700 Subject: [PATCH 042/685] Special case DUMMY_SP to emit line 0/column 0 locations on DWARF platforms. Line 0 has a special meaning in DWARF. From the version 5 spec: The compiler may emit the value 0 in cases where an instruction cannot be attributed to any source line. DUMMY_SP spans cannot be attributed to any line. However, because rustc internally stores line numbers starting at zero, lookup_debug_loc() adjusts every line number by one. Special casing DUMMY_SP to actually emit line 0 ensures rustc communicates to the debugger that there's no meaningful source code for this instruction, rather than telling the debugger to jump to line 1 randomly. --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index b23e05182ca1b..3706d31e66e17 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -20,7 +20,7 @@ use rustc_session::config::{self, DebugInfo}; use rustc_session::Session; use rustc_span::symbol::Symbol; use rustc_span::{ - BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, + BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, DUMMY_SP, }; use rustc_target::abi::Size; use smallvec::SmallVec; @@ -570,7 +570,12 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { inlined_at: Option<&'ll DILocation>, span: Span, ) -> &'ll DILocation { - let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); + let (line, col) = if span == DUMMY_SP && !self.sess().target.is_like_msvc { + (0, 0) + } else { + let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); + (line, col) + }; unsafe { llvm::LLVMRustDIBuilderCreateDebugLocation(line, col, scope, inlined_at) } } From 709406fc6c792bd5dd0d07fbc59c4437b3d47f95 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sat, 3 Aug 2024 21:23:35 -0700 Subject: [PATCH 043/685] When deduplicating unreachable blocks, erase the source information. After deduplication the block conceptually belongs to multiple locations in the source. Although these blocks are unreachable, in #123341 we did come across a real side effect, an unreachable block that survives into the compiled code can cause a debugger to set a breakpoint on the wrong instruction. Erasing the source information ensures that a debugger will never be misled into thinking that the unreachable block is worth setting a breakpoint on, especially after #128627. Technically we don't need to erase the source information if all the deduplicated blocks have identical source information, but tracking that seems like more effort than it's worth. --- compiler/rustc_mir_transform/src/simplify.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index 5bbe3bb747fd9..4fe8cf6213f80 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -31,6 +31,7 @@ use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; +use rustc_span::DUMMY_SP; use smallvec::SmallVec; pub enum SimplifyCfg { @@ -318,6 +319,7 @@ pub(crate) fn remove_dead_blocks(body: &mut Body<'_>) { let mut orig_index = 0; let mut used_index = 0; let mut kept_unreachable = None; + let mut deduplicated_unreachable = false; basic_blocks.raw.retain(|bbdata| { let orig_bb = BasicBlock::new(orig_index); if !reachable.contains(orig_bb) { @@ -330,6 +332,7 @@ pub(crate) fn remove_dead_blocks(body: &mut Body<'_>) { let kept_unreachable = *kept_unreachable.get_or_insert(used_bb); if kept_unreachable != used_bb { replacements[orig_index] = kept_unreachable; + deduplicated_unreachable = true; orig_index += 1; return false; } @@ -341,6 +344,14 @@ pub(crate) fn remove_dead_blocks(body: &mut Body<'_>) { true }); + // If we deduplicated unreachable blocks we erase their source_info as we + // can no longer attribute their code to a particular location in the + // source. + if deduplicated_unreachable { + basic_blocks[kept_unreachable.unwrap()].terminator_mut().source_info = + SourceInfo { span: DUMMY_SP, scope: OUTERMOST_SOURCE_SCOPE }; + } + for block in basic_blocks { for target in block.terminator_mut().successors_mut() { *target = replacements[target.index()]; From e5878555387e4ced52b3dd6c5f6a04a5f47eeed7 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sun, 4 Aug 2024 05:26:50 -0700 Subject: [PATCH 044/685] Use Span::is_dummy(). --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 3706d31e66e17..57b8fb2fe71b8 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -570,7 +570,7 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { inlined_at: Option<&'ll DILocation>, span: Span, ) -> &'ll DILocation { - let (line, col) = if span == DUMMY_SP && !self.sess().target.is_like_msvc { + let (line, col) = if span.is_dummy() && !self.sess().target.is_like_msvc { (0, 0) } else { let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); From 5dc4a1969c6ea5ba0b1d11f9d43045ed5a2be5cc Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sun, 4 Aug 2024 06:09:55 -0700 Subject: [PATCH 045/685] Fix warning. --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 57b8fb2fe71b8..66dd653bb2166 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -20,7 +20,7 @@ use rustc_session::config::{self, DebugInfo}; use rustc_session::Session; use rustc_span::symbol::Symbol; use rustc_span::{ - BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, DUMMY_SP, + BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, }; use rustc_target::abi::Size; use smallvec::SmallVec; From 8a61674559c58b9cfcc7d297425b33520fcf39fa Mon Sep 17 00:00:00 2001 From: Georgii Rylov Date: Mon, 5 Aug 2024 09:48:26 +0100 Subject: [PATCH 046/685] WASI fixing unsafe_op_in_unsafe_fn for std::{os, sys} --- library/std/src/os/wasi/fs.rs | 1 - library/std/src/os/wasi/mod.rs | 2 +- library/std/src/os/wasip2/mod.rs | 1 + library/std/src/sys/pal/wasi/args.rs | 2 +- library/std/src/sys/pal/wasi/env.rs | 2 ++ library/std/src/sys/pal/wasi/fd.rs | 2 +- library/std/src/sys/pal/wasi/fs.rs | 2 +- library/std/src/sys/pal/wasi/helpers.rs | 2 ++ library/std/src/sys/pal/wasi/io.rs | 2 +- library/std/src/sys/pal/wasi/net.rs | 2 +- library/std/src/sys/pal/wasi/os.rs | 2 +- library/std/src/sys/pal/wasi/stdio.rs | 2 +- library/std/src/sys/pal/wasi/thread.rs | 18 ++++++++++-------- library/std/src/sys/pal/wasi/time.rs | 2 +- 14 files changed, 24 insertions(+), 18 deletions(-) diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index a58ca543d6777..9ec3e387e2ba9 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -2,7 +2,6 @@ //! //! [`std::fs`]: crate::fs -#![deny(unsafe_op_in_unsafe_fn)] #![unstable(feature = "wasi_ext", issue = "71213")] // Used for `File::read` on intra-doc links diff --git a/library/std/src/os/wasi/mod.rs b/library/std/src/os/wasi/mod.rs index e36b93e60ea1c..33b50c9e53b8f 100644 --- a/library/std/src/os/wasi/mod.rs +++ b/library/std/src/os/wasi/mod.rs @@ -30,7 +30,7 @@ #![cfg_attr(not(target_env = "p2"), stable(feature = "rust1", since = "1.0.0"))] #![cfg_attr(target_env = "p2", unstable(feature = "wasip2", issue = "none"))] -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] #![doc(cfg(target_os = "wasi"))] pub mod ffi; diff --git a/library/std/src/os/wasip2/mod.rs b/library/std/src/os/wasip2/mod.rs index 1d44dd72814b8..809a288f20d04 100644 --- a/library/std/src/os/wasip2/mod.rs +++ b/library/std/src/os/wasip2/mod.rs @@ -2,4 +2,5 @@ //! //! This module is currently empty, but will be filled over time as wasi-libc support for WASI Preview 2 is stabilized. +#![forbid(unsafe_op_in_unsafe_fn)] #![stable(feature = "raw_ext", since = "1.1.0")] diff --git a/library/std/src/sys/pal/wasi/args.rs b/library/std/src/sys/pal/wasi/args.rs index 6b6d1b8ff4e2e..52cfa202af825 100644 --- a/library/std/src/sys/pal/wasi/args.rs +++ b/library/std/src/sys/pal/wasi/args.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::ffi::{CStr, OsStr, OsString}; use crate::os::wasi::ffi::OsStrExt; diff --git a/library/std/src/sys/pal/wasi/env.rs b/library/std/src/sys/pal/wasi/env.rs index 730e356d7fe95..8d44498267360 100644 --- a/library/std/src/sys/pal/wasi/env.rs +++ b/library/std/src/sys/pal/wasi/env.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + pub mod os { pub const FAMILY: &str = ""; pub const OS: &str = ""; diff --git a/library/std/src/sys/pal/wasi/fd.rs b/library/std/src/sys/pal/wasi/fd.rs index 8966e4b80ad37..19b60157e2e00 100644 --- a/library/std/src/sys/pal/wasi/fd.rs +++ b/library/std/src/sys/pal/wasi/fd.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] #![allow(dead_code)] use super::err2io; diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs index 11900886f0b5c..6a97621ad50a5 100644 --- a/library/std/src/sys/pal/wasi/fs.rs +++ b/library/std/src/sys/pal/wasi/fs.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::fd::WasiFd; use crate::ffi::{CStr, OsStr, OsString}; diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasi/helpers.rs index 4b770ee23bc5d..d047bf2fce857 100644 --- a/library/std/src/sys/pal/wasi/helpers.rs +++ b/library/std/src/sys/pal/wasi/helpers.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + use crate::{io as std_io, mem}; #[inline] diff --git a/library/std/src/sys/pal/wasi/io.rs b/library/std/src/sys/pal/wasi/io.rs index 2cd45df88fad1..b7c2f03daa048 100644 --- a/library/std/src/sys/pal/wasi/io.rs +++ b/library/std/src/sys/pal/wasi/io.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::marker::PhantomData; use crate::os::fd::{AsFd, AsRawFd}; diff --git a/library/std/src/sys/pal/wasi/net.rs b/library/std/src/sys/pal/wasi/net.rs index b4cf94c8781ec..a648679982812 100644 --- a/library/std/src/sys/pal/wasi/net.rs +++ b/library/std/src/sys/pal/wasi/net.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::err2io; use super::fd::WasiFd; diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs index f5b17d9df94b4..f7701360f5a9c 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use core::slice::memchr; diff --git a/library/std/src/sys/pal/wasi/stdio.rs b/library/std/src/sys/pal/wasi/stdio.rs index 4cc0e4ed5a45a..ca49f871e1957 100644 --- a/library/std/src/sys/pal/wasi/stdio.rs +++ b/library/std/src/sys/pal/wasi/stdio.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::fd::WasiFd; use crate::io::{self, IoSlice, IoSliceMut}; diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs index c37acd8dfeeb7..31c9cbd4699bd 100644 --- a/library/std/src/sys/pal/wasi/thread.rs +++ b/library/std/src/sys/pal/wasi/thread.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + use crate::ffi::CStr; use crate::num::NonZero; use crate::sys::unsupported; @@ -73,13 +75,13 @@ impl Thread { if #[cfg(target_feature = "atomics")] { pub unsafe fn new(stack: usize, p: Box) -> io::Result { let p = Box::into_raw(Box::new(p)); - let mut native: libc::pthread_t = mem::zeroed(); - let mut attr: libc::pthread_attr_t = mem::zeroed(); - assert_eq!(libc::pthread_attr_init(&mut attr), 0); + let mut native: libc::pthread_t = unsafe { mem::zeroed() }; + let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() }; + assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0); let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); - match libc::pthread_attr_setstacksize(&mut attr, stack_size) { + match unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) } { 0 => {} n => { assert_eq!(n, libc::EINVAL); @@ -90,20 +92,20 @@ impl Thread { let page_size = os::page_size(); let stack_size = (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); - assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0); + assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); } }; - let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _); + let ret = unsafe { libc::pthread_create(&mut native, &attr, thread_start, p as *mut _) }; // Note: if the thread creation fails and this assert fails, then p will // be leaked. However, an alternative design could cause double-free // which is clearly worse. - assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + assert_eq!(unsafe {libc::pthread_attr_destroy(&mut attr) }, 0); return if ret != 0 { // The thread failed to start and as a result p was not consumed. Therefore, it is // safe to reconstruct the box so that it gets deallocated. - drop(Box::from_raw(p)); + unsafe { drop(Box::from_raw(p)); } Err(io::Error::from_raw_os_error(ret)) } else { Ok(Thread { id: native }) diff --git a/library/std/src/sys/pal/wasi/time.rs b/library/std/src/sys/pal/wasi/time.rs index 016b06efbdc63..0d8d0b59ac14a 100644 --- a/library/std/src/sys/pal/wasi/time.rs +++ b/library/std/src/sys/pal/wasi/time.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::time::Duration; From 234a1d35d9f1acc1da25b5069908ca2de209d322 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:02:08 +0200 Subject: [PATCH 047/685] recognize metavariables in tail expressions --- clippy_lints/src/macro_metavars_in_unsafe.rs | 21 +++++++++++++++++-- .../macro_metavars_in_unsafe/default/test.rs | 15 ++++++++++++- .../default/test.stderr | 16 +++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index fed58f7ff1481..e215097142be9 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -122,8 +122,23 @@ struct BodyVisitor<'a, 'tcx> { /// within a relevant macro. macro_unsafe_blocks: Vec, /// When this is >0, it means that the node currently being visited is "within" a - /// macro definition. This is not necessary for correctness, it merely helps reduce the number - /// of spans we need to insert into the map, since only spans from macros are relevant. + /// macro definition. + /// This is used to detect if an expression represents a metavariable. + /// + /// For example, the following pre-expansion code that we want to lint + /// ```ignore + /// macro_rules! m { ($e:expr) => { unsafe { $e; } } } + /// m!(1); + /// ``` + /// would look like this post-expansion code: + /// ```ignore + /// unsafe { /* macro */ + /// 1 /* root */; /* macro */ + /// } + /// ``` + /// Visiting the block and the statement will increment the `expn_depth` so that it is >0, + /// and visiting the expression with a root context while `expn_depth > 0` tells us + /// that it must be a metavariable. expn_depth: u32, cx: &'a LateContext<'tcx>, lint: &'a mut ExprMetavarsInUnsafe, @@ -157,7 +172,9 @@ impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> { && (self.lint.warn_unsafe_macro_metavars_in_private_macros || is_public_macro(self.cx, macro_def_id)) { self.macro_unsafe_blocks.push(block.hir_id); + self.expn_depth += 1; walk_block(self, block); + self.expn_depth -= 1; self.macro_unsafe_blocks.pop(); } else if ctxt.is_root() && self.expn_depth > 0 { let unsafe_block = self.macro_unsafe_blocks.last().copied(); diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs index a312df5a43a07..3dafea56514db 100644 --- a/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs @@ -1,7 +1,7 @@ //! Tests macro_metavars_in_unsafe with default configuration #![feature(decl_macro)] #![warn(clippy::macro_metavars_in_unsafe)] -#![allow(clippy::no_effect)] +#![allow(clippy::no_effect, clippy::not_unsafe_ptr_arg_deref)] #[macro_export] macro_rules! allow_works { @@ -237,6 +237,19 @@ macro_rules! nested_macros { }}; } +pub mod issue13219 { + #[macro_export] + macro_rules! m { + ($e:expr) => { + // Metavariable in a block tail expression + unsafe { $e } + }; + } + pub fn f(p: *const i32) -> i32 { + m!(*p) + } +} + fn main() { allow_works!(1); simple!(1); diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr index d6b97f6fde1e1..6f0ebcbba0239 100644 --- a/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr @@ -1,3 +1,15 @@ +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:245:13 + | +LL | unsafe { $e } + | ^^^^^^^^^^^^^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` + error: this macro expands metavariables in an unsafe block --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:19:9 | @@ -10,8 +22,6 @@ LL | | } = note: this allows the user of the macro to write unsafe code outside of an unsafe block = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite - = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` error: this macro expands metavariables in an unsafe block --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:30:9 @@ -183,5 +193,5 @@ LL | | } = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors From 2431949e2a406c8b3243620d72dd3eaaceafb325 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 6 Aug 2024 04:57:39 +0000 Subject: [PATCH 048/685] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index b74f9759ebed6..f6fbc9844f860 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -29e924841f06bb181d87494eba2783761bc1ddec +c9687a95a602091777e28703aa5abf20f1ce1797 From 7d066c4edd1dcb0291ae2ca8cb0e1b6548f0ce4c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 6 Aug 2024 11:00:18 +0200 Subject: [PATCH 049/685] add return-place-protection tail-call test, and fix previous test --- ... return_pointer_aliasing_read.none.stderr} | 6 +-- ...ing.rs => return_pointer_aliasing_read.rs} | 0 ...return_pointer_aliasing_read.stack.stderr} | 10 ++--- ... return_pointer_aliasing_read.tree.stderr} | 12 ++--- ...g2.rs => return_pointer_aliasing_write.rs} | 8 ++-- ...eturn_pointer_aliasing_write.stack.stderr} | 23 +++++----- ...return_pointer_aliasing_write.tree.stderr} | 16 +++---- ...return_pointer_aliasing_write_tail_call.rs | 39 ++++++++++++++++ ...nter_aliasing_write_tail_call.stack.stderr | 37 +++++++++++++++ ...inter_aliasing_write_tail_call.tree.stderr | 45 +++++++++++++++++++ 10 files changed, 157 insertions(+), 39 deletions(-) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing.none.stderr => return_pointer_aliasing_read.none.stderr} (82%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing.rs => return_pointer_aliasing_read.rs} (100%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing.stack.stderr => return_pointer_aliasing_read.stack.stderr} (85%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing.tree.stderr => return_pointer_aliasing_read.tree.stderr} (85%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing2.rs => return_pointer_aliasing_write.rs} (73%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing2.stack.stderr => return_pointer_aliasing_write.stack.stderr} (56%) rename src/tools/miri/tests/fail/function_calls/{return_pointer_aliasing2.tree.stderr => return_pointer_aliasing_write.tree.stderr} (81%) create mode 100644 src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs create mode 100644 src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr create mode 100644 src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr similarity index 82% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr index eb215a2d2e805..e8b766d0b0e28 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory @@ -7,9 +7,9 @@ LL | unsafe { ptr.read() }; = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: - = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC + = note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC note: inside `main` - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs similarity index 100% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr similarity index 85% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr index 01357f430fc71..a8817fe639038 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID @@ -7,7 +7,7 @@ LL | unsafe { ptr.read() }; = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a SharedReadWrite retag at offsets [0x0..0x4] - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | / mir! { LL | | { @@ -18,14 +18,14 @@ LL | | } LL | | } | |_____^ help: is this argument - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^^^^^^^^^^^^ = note: BACKTRACE (of the first span): - = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC + = note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC note: inside `main` - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr similarity index 85% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr index bbedba5a7ddb0..715ee33061994 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: read access through (root of the allocation) at ALLOC[0x0] is forbidden - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^ read access through (root of the allocation) at ALLOC[0x0] is forbidden @@ -9,7 +9,7 @@ LL | unsafe { ptr.read() }; = help: this foreign read access would cause the protected tag (currently Active) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | / mir! { LL | | { @@ -20,20 +20,20 @@ LL | | } LL | | } | |_____^ help: the protected tag was created here, in the initial state Reserved - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^^^^^^^^^^^^ help: the protected tag later transitioned to Active due to a child write access at offsets [0x0..0x4] - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | unsafe { ptr.read() }; | ^^^^^^^^^^^^^^^^^^^^^ = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference = note: BACKTRACE (of the first span): - = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC + = note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC note: inside `main` - --> $DIR/return_pointer_aliasing.rs:LL:CC + --> $DIR/return_pointer_aliasing_read.rs:LL:CC | LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs similarity index 73% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs index 7db641538ce55..8de0b12b8b040 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs @@ -1,4 +1,4 @@ -// This does need an aliasing model. +// This does need an aliasing model and protectors. //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows #![feature(raw_ref_op)] @@ -14,8 +14,8 @@ pub fn main() { let _x = 0; let ptr = &raw mut _x; // We arrange for `myfun` to have a pointer that aliases - // its return place. Even just reading from that pointer is UB. - Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + // its return place. Writing to that pointer is UB. + Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) } after_call = { @@ -27,7 +27,7 @@ pub fn main() { fn myfun(ptr: *mut i32) -> i32 { // This overwrites the return place, which shouldn't be possible through another pointer. unsafe { ptr.write(0) }; - //~[stack]^ ERROR: tag does not exist in the borrow stack + //~[stack]^ ERROR: strongly protected //~[tree]| ERROR: /write access .* forbidden/ 13 } diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr similarity index 56% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr index 04040827b0f97..09f9681ff8ebc 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr @@ -1,16 +1,13 @@ -error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> $DIR/return_pointer_aliasing2.rs:LL:CC +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | unsafe { ptr.write(0) }; - | ^^^^^^^^^^^^ - | | - | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - | this error occurs as part of an access at ALLOC[0x0..0x4] + | ^^^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a SharedReadWrite retag at offsets [0x0..0x4] - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | / mir! { LL | | { @@ -20,18 +17,18 @@ LL | | let ptr = &raw mut _x; LL | | } LL | | } | |_____^ -help: was later invalidated at offsets [0x0..0x4] by a Unique in-place function argument/return passing protection - --> $DIR/return_pointer_aliasing2.rs:LL:CC +help: is this argument + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | unsafe { ptr.write(0) }; | ^^^^^^^^^^^^^^^^^^^^^^^ = note: BACKTRACE (of the first span): - = note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC + = note: inside `myfun` at $DIR/return_pointer_aliasing_write.rs:LL:CC note: inside `main` - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | -LL | Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr similarity index 81% rename from src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr rename to src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr index 146bcfc7c47d0..66ca1027edce2 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: write access through (root of the allocation) at ALLOC[0x0] is forbidden - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | unsafe { ptr.write(0) }; | ^^^^^^^^^^^^ write access through (root of the allocation) at ALLOC[0x0] is forbidden @@ -9,7 +9,7 @@ LL | unsafe { ptr.write(0) }; = help: this foreign write access would cause the protected tag (currently Active) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | / mir! { LL | | { @@ -20,23 +20,23 @@ LL | | } LL | | } | |_____^ help: the protected tag was created here, in the initial state Reserved - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | unsafe { ptr.write(0) }; | ^^^^^^^^^^^^^^^^^^^^^^^ help: the protected tag later transitioned to Active due to a child write access at offsets [0x0..0x4] - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | LL | unsafe { ptr.write(0) }; | ^^^^^^^^^^^^^^^^^^^^^^^ = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference = note: BACKTRACE (of the first span): - = note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC + = note: inside `myfun` at $DIR/return_pointer_aliasing_write.rs:LL:CC note: inside `main` - --> $DIR/return_pointer_aliasing2.rs:LL:CC + --> $DIR/return_pointer_aliasing_write.rs:LL:CC | -LL | Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs new file mode 100644 index 0000000000000..facc323bbc518 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs @@ -0,0 +1,39 @@ +// This does need an aliasing model and protectors. +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +#![feature(raw_ref_op)] +#![feature(core_intrinsics)] +#![feature(custom_mir)] +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] + +use std::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn main() { + mir! { + { + let _x = 0; + let ptr = &raw mut _x; + // We arrange for `myfun` to have a pointer that aliases + // its return place. Writing to that pointer is UB. + Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + } + + after_call = { + Return() + } + } +} + +fn myfun(ptr: *mut i32) -> i32 { + become myfun2(ptr) +} + +fn myfun2(ptr: *mut i32) -> i32 { + // This overwrites the return place, which shouldn't be possible through another pointer. + unsafe { ptr.write(0) }; + //~[stack]^ ERROR: strongly protected + //~[tree]| ERROR: /write access .* forbidden/ + 13 +} diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr new file mode 100644 index 0000000000000..b8d8a93ec8329 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | / mir! { +LL | | { +LL | | let _x = 0; +LL | | let ptr = &raw mut _x; +... | +LL | | } +LL | | } + | |_____^ +help: is this argument + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `myfun2` at $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr new file mode 100644 index 0000000000000..b1f2cab031e4d --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr @@ -0,0 +1,45 @@ +error: Undefined Behavior: write access through (root of the allocation) at ALLOC[0x0] is forbidden + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^ write access through (root of the allocation) at ALLOC[0x0] is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | / mir! { +LL | | { +LL | | let _x = 0; +LL | | let ptr = &raw mut _x; +... | +LL | | } +LL | | } + | |_____^ +help: the protected tag was created here, in the initial state Reserved + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: the protected tag later transitioned to Active due to a child write access at offsets [0x0..0x4] + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference + = note: BACKTRACE (of the first span): + = note: inside `myfun2` at $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC + | +LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + From ac23a2e5cd288ea6c160ecd51ea64662d3443dd4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 6 Aug 2024 11:12:45 +0200 Subject: [PATCH 050/685] bump conflicting_repr_hints lint to be shown in dependencies --- compiler/rustc_lint_defs/src/builtin.rs | 2 +- .../feature-gate-repr-simd.stderr | 14 +++++++++++ tests/ui/issues/issue-47094.stderr | 25 +++++++++++++++++++ tests/ui/repr/conflicting-repr-hints.stderr | 22 ++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index ff0bdfcc9d261..0bfc3d11c2f65 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -250,7 +250,7 @@ declare_lint! { Deny, "conflicts between `#[repr(..)]` hints that were previously accepted and used in practice", @future_incompatible = FutureIncompatibleInfo { - reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps, reference: "issue #68585 ", }; } diff --git a/tests/ui/feature-gates/feature-gate-repr-simd.stderr b/tests/ui/feature-gates/feature-gate-repr-simd.stderr index 5b490c0c0c33f..5a0ceb2dd74fb 100644 --- a/tests/ui/feature-gates/feature-gate-repr-simd.stderr +++ b/tests/ui/feature-gates/feature-gate-repr-simd.stderr @@ -35,3 +35,17 @@ error: aborting due to 3 previous errors Some errors have detailed explanations: E0566, E0658. For more information about an error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/feature-gate-repr-simd.rs:4:8 + | +LL | #[repr(C)] + | ^ +LL | +LL | #[repr(simd)] + | ^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + diff --git a/tests/ui/issues/issue-47094.stderr b/tests/ui/issues/issue-47094.stderr index 970e318471049..1c6693403b85b 100644 --- a/tests/ui/issues/issue-47094.stderr +++ b/tests/ui/issues/issue-47094.stderr @@ -23,3 +23,28 @@ LL | #[repr(u8)] error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/issue-47094.rs:1:8 + | +LL | #[repr(C, u8)] + | ^ ^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + +Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/issue-47094.rs:8:8 + | +LL | #[repr(C)] + | ^ +LL | +LL | #[repr(u8)] + | ^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + diff --git a/tests/ui/repr/conflicting-repr-hints.stderr b/tests/ui/repr/conflicting-repr-hints.stderr index 4dcd8f4fc280a..fbfa69e7fb145 100644 --- a/tests/ui/repr/conflicting-repr-hints.stderr +++ b/tests/ui/repr/conflicting-repr-hints.stderr @@ -81,3 +81,25 @@ error: aborting due to 12 previous errors Some errors have detailed explanations: E0566, E0587, E0634. For more information about an error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/conflicting-repr-hints.rs:13:8 + | +LL | #[repr(C, u64)] + | ^ ^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + +Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/conflicting-repr-hints.rs:19:8 + | +LL | #[repr(u32, u64)] + | ^^^ ^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + From b335ec9ec8d1132efee4e900a1c173efc7f4a357 Mon Sep 17 00:00:00 2001 From: Flying-Toast <38232168+Flying-Toast@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:49:05 -0400 Subject: [PATCH 051/685] Add a special case for CStr/CString in the improper_ctypes lint Instead of saying to "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct", we now tell users to "Use `*const ffi::c_char` instead, and pass the value from `CStr::as_ptr()`" when the type involved is a `CStr` or a `CString`. Co-authored-by: Jieyou Xu --- compiler/rustc_lint/messages.ftl | 5 ++ compiler/rustc_lint/src/types.rs | 54 ++++++++---- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/ffi/c_str.rs | 1 + ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 12 +-- tests/ui/lint/lint-ctypes-cstr.rs | 36 ++++++++ tests/ui/lint/lint-ctypes-cstr.stderr | 84 +++++++++++++++++++ 7 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 tests/ui/lint/lint-ctypes-cstr.rs create mode 100644 tests/ui/lint/lint-ctypes-cstr.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 7e4feb0a82716..52a03772dc23c 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -361,6 +361,11 @@ lint_improper_ctypes_box = box cannot be represented as a single pointer lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead lint_improper_ctypes_char_reason = the `char` type has no C equivalent + +lint_improper_ctypes_cstr_help = + consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout + lint_improper_ctypes_dyn = trait objects have no C equivalent lint_improper_ctypes_enum_repr_help = diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index f3196cfed533e..d84d60ef5a7c1 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -984,6 +984,14 @@ struct ImproperCTypesVisitor<'a, 'tcx> { mode: CItemKind, } +/// Accumulator for recursive ffi type checking +struct CTypesVisitorState<'tcx> { + cache: FxHashSet>, + /// The original type being checked, before we recursed + /// to any other types it contains. + base_ty: Ty<'tcx>, +} + enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), @@ -1212,7 +1220,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given field's type is "ffi-safe". fn check_field_type_for_ffi( &self, - cache: &mut FxHashSet>, + acc: &mut CTypesVisitorState<'tcx>, field: &ty::FieldDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { @@ -1222,13 +1230,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .tcx .try_normalize_erasing_regions(self.cx.param_env, field_ty) .unwrap_or(field_ty); - self.check_type_for_ffi(cache, field_ty) + self.check_type_for_ffi(acc, field_ty) } /// Checks if the given `VariantDef`'s field types are "ffi-safe". fn check_variant_for_ffi( &self, - cache: &mut FxHashSet>, + acc: &mut CTypesVisitorState<'tcx>, ty: Ty<'tcx>, def: ty::AdtDef<'tcx>, variant: &ty::VariantDef, @@ -1238,7 +1246,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. - match self.check_field_type_for_ffi(cache, field, args) { + match self.check_field_type_for_ffi(acc, field, args) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -1256,7 +1264,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { - all_phantom &= match self.check_field_type_for_ffi(cache, field, args) { + all_phantom &= match self.check_field_type_for_ffi(acc, field, args) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -1276,7 +1284,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> FfiResult<'tcx> { + fn check_type_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -1285,7 +1297,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // `struct S(*mut S);`. // FIXME: A recursion limit is necessary as well, for irregular // recursive types. - if !cache.insert(ty) { + if !acc.cache.insert(ty) { return FfiSafe; } @@ -1307,6 +1319,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match def.adt_kind() { AdtKind::Struct | AdtKind::Union => { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + && !acc.base_ty.is_mutable_ptr() + { + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_cstr_reason, + help: Some(fluent::lint_improper_ctypes_cstr_help), + }; + } + if !def.repr().c() && !def.repr().transparent() { return FfiUnsafe { ty, @@ -1353,7 +1376,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args) + self.check_variant_for_ffi(acc, ty, def, def.non_enum_variant(), args) } AdtKind::Enum => { if def.variants().is_empty() { @@ -1377,7 +1400,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(ty) = repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) { - return self.check_type_for_ffi(cache, ty); + return self.check_type_for_ffi(acc, ty); } return FfiUnsafe { @@ -1398,7 +1421,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - match self.check_variant_for_ffi(cache, ty, def, variant, args) { + match self.check_variant_for_ffi(acc, ty, def, variant, args) { FfiSafe => (), r => return r, } @@ -1468,9 +1491,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(cache, ty), + ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty), - ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty), + ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), ty::FnPtr(sig) => { if self.is_internal_abi(sig.abi()) { @@ -1483,7 +1506,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.check_type_for_ffi(cache, *arg) { + match self.check_type_for_ffi(acc, *arg) { FfiSafe => {} r => return r, } @@ -1494,7 +1517,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return FfiSafe; } - self.check_type_for_ffi(cache, ret_ty) + self.check_type_for_ffi(acc, ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1617,7 +1640,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return; } - match self.check_type_for_ffi(&mut FxHashSet::default(), ty) { + let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty }; + match self.check_type_for_ffi(&mut acc, ty) { FfiResult::FfiSafe => {} FfiResult::FfiPhantom(ty) => { self.emit_ffi_unsafe_type_lint( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 94cf21da4efb8..407f95a0f21e3 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -671,6 +671,7 @@ symbols! { crate_visibility_modifier, crt_dash_static: "crt-static", csky_target_feature, + cstr_type, cstring_type, ctlz, ctlz_nonzero, diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 22084dcff8f88..7808d42ab5de4 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -91,6 +91,7 @@ use crate::{fmt, intrinsics, ops, slice, str}; /// [str]: prim@str "str" #[derive(PartialEq, Eq, Hash)] #[stable(feature = "core_c_str", since = "1.64.0")] +#[rustc_diagnostic_item = "cstr_type"] #[rustc_has_incoherent_inherent_impls] #[lang = "CStr"] // `fn from` in `impl From<&CStr> for Box` current implementation relies diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr index 8349298847918..044c1ae2dd42f 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -1,21 +1,21 @@ -warning: `extern` fn uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` fn uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:12 | LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes_definitions)]` on by default -warning: `extern` block uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` block uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:10:18 | LL | fn meh(blah: Foo); | ^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` on by default warning: 2 warnings emitted diff --git a/tests/ui/lint/lint-ctypes-cstr.rs b/tests/ui/lint/lint-ctypes-cstr.rs new file mode 100644 index 0000000000000..b04decd0bcacc --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.rs @@ -0,0 +1,36 @@ +#![crate_type = "lib"] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::ffi::{CStr, CString}; + +extern "C" { + fn take_cstr(s: CStr); + //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstr_ref(s: &CStr); + //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstring(s: CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstring_ref(s: &CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + + fn no_special_help_for_mut_cstring(s: *mut CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + + fn no_special_help_for_mut_cstring_ref(s: &mut CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct +} + +extern "C" fn rust_take_cstr_ref(s: &CStr) {} +//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +extern "C" fn rust_take_cstring(s: CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {} +extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {} diff --git a/tests/ui/lint/lint-ctypes-cstr.stderr b/tests/ui/lint/lint-ctypes-cstr.stderr new file mode 100644 index 0000000000000..8957758d57732 --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.stderr @@ -0,0 +1,84 @@ +error: `extern` block uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:7:21 + | +LL | fn take_cstr(s: CStr); + | ^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout +note: the lint level is defined here + --> $DIR/lint-ctypes-cstr.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:10:25 + | +LL | fn take_cstr_ref(s: &CStr); + | ^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:13:24 + | +LL | fn take_cstring(s: CString); + | ^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:16:28 + | +LL | fn take_cstring_ref(s: &CString); + | ^^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:20:43 + | +LL | fn no_special_help_for_mut_cstring(s: *mut CString); + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:24:47 + | +LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString); + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout + +error: `extern` fn uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:29:37 + | +LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {} + | ^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout +note: the lint level is defined here + --> $DIR/lint-ctypes-cstr.rs:2:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:32:36 + | +LL | extern "C" fn rust_take_cstring(s: CString) {} + | ^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: aborting due to 8 previous errors + From 37984bbde11e6bcd0a009d2bbdbe7a73f9605b05 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Aug 2024 08:58:53 +0300 Subject: [PATCH 052/685] unify path syncing logic for vendor and dist Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/dist.rs | 34 ++++++-------------- src/bootstrap/src/core/build_steps/vendor.rs | 32 ++++++++++++------ 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index c14709ffb6324..cd7d602e859f8 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -19,6 +19,7 @@ use object::BinaryFormat; use crate::core::build_steps::doc::DocumentationFormat; use crate::core::build_steps::tool::{self, Tool}; +use crate::core::build_steps::vendor::default_paths_to_vendor; use crate::core::build_steps::{compile, llvm}; use crate::core::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; use crate::core::config::TargetSelection; @@ -1016,35 +1017,18 @@ impl Step for PlainSourceTarball { if builder.rust_info().is_managed_git_subrepository() || builder.rust_info().is_from_tarball() { - // FIXME: This code looks _very_ similar to what we have in `src/core/build_steps/vendor.rs` - // perhaps it should be removed in favor of making `dist` perform the `vendor` step? - builder.require_and_update_all_submodules(); // Vendor all Cargo dependencies let mut cmd = command(&builder.initial_cargo); - cmd.arg("vendor") - .arg("--versioned-dirs") - .arg("--sync") - .arg(builder.src.join("./src/tools/cargo/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./src/tools/rust-analyzer/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./compiler/rustc_codegen_cranelift/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./compiler/rustc_codegen_gcc/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./library/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./src/bootstrap/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./src/tools/opt-dist/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./src/tools/rustc-perf/Cargo.toml")) - .arg("--sync") - .arg(builder.src.join("./src/tools/rustbook/Cargo.toml")) - // Will read the libstd Cargo.toml - // which uses the unstable `public-dependency` feature. + cmd.arg("vendor").arg("--versioned-dirs"); + + for p in default_paths_to_vendor(builder) { + cmd.arg("--sync").arg(p); + } + + cmd + // Will read the libstd Cargo.toml which uses the unstable `public-dependency` feature. .env("RUSTC_BOOTSTRAP", "1") .current_dir(plain_dst_src); diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs index 33768465225fe..82a6b4d4f28cb 100644 --- a/src/bootstrap/src/core/build_steps/vendor.rs +++ b/src/bootstrap/src/core/build_steps/vendor.rs @@ -4,6 +4,26 @@ use crate::core::build_steps::tool::SUBMODULES_FOR_RUSTBOOK; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::utils::exec::command; +/// List of default paths used for vendoring for `x vendor` and dist tarballs. +pub fn default_paths_to_vendor(builder: &Builder<'_>) -> Vec { + let mut paths = vec![]; + for p in [ + "src/tools/cargo/Cargo.toml", + "src/tools/rust-analyzer/Cargo.toml", + "compiler/rustc_codegen_cranelift/Cargo.toml", + "compiler/rustc_codegen_gcc/Cargo.toml", + "library/Cargo.toml", + "src/bootstrap/Cargo.toml", + "src/tools/rustbook/Cargo.toml", + "src/tools/rustc-perf/Cargo.toml", + "src/tools/opt-dist/Cargo.toml", + ] { + paths.push(builder.src.join(p)); + } + + paths +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct Vendor { sync_args: Vec, @@ -42,16 +62,8 @@ impl Step for Vendor { } // Sync these paths by default. - for p in [ - "src/tools/cargo/Cargo.toml", - "src/tools/rust-analyzer/Cargo.toml", - "compiler/rustc_codegen_cranelift/Cargo.toml", - "compiler/rustc_codegen_gcc/Cargo.toml", - "library/Cargo.toml", - "src/bootstrap/Cargo.toml", - "src/tools/rustbook/Cargo.toml", - ] { - cmd.arg("--sync").arg(builder.src.join(p)); + for p in default_paths_to_vendor(builder) { + cmd.arg("--sync").arg(p); } // Also sync explicitly requested paths. From bc0bc91e109fb21243f6bf15747c6fe2158d6a4b Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Aug 2024 09:00:16 +0300 Subject: [PATCH 053/685] remove redundant FIXMEs Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/tool.rs | 7 +------ src/bootstrap/src/core/builder.rs | 14 +++++--------- src/bootstrap/src/core/download.rs | 2 -- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 4d573b107b587..ff8ca2ad74aaf 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -1091,18 +1091,13 @@ macro_rules! tool_extended { } } -// NOTE: tools need to be also added to `Builder::get_step_descriptions` in `builder.rs` -// to make `./x.py build ` work. tool_extended!((self, builder), Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true; CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true; Clippy, "src/tools/clippy", "clippy-driver", stable=true, add_bins_to_sysroot = ["clippy-driver", "cargo-clippy"]; Miri, "src/tools/miri", "miri", stable=false, add_bins_to_sysroot = ["miri"]; CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=true, add_bins_to_sysroot = ["cargo-miri"]; - // FIXME: tool_std is not quite right, we shouldn't allow nightly features. - // But `builder.cargo` doesn't know how to handle ToolBootstrap in stages other than 0, - // and this is close enough for now. - Rls, "src/tools/rls", "rls", stable=true, tool_std=true; + Rls, "src/tools/rls", "rls", stable=true; Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, add_bins_to_sysroot = ["rustfmt", "cargo-fmt"]; ); diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index 84c23c059e97e..d6c6de8f80df4 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -1449,15 +1449,11 @@ impl<'a> Builder<'a> { assert_eq!(target, compiler.host); } - if self.config.rust_optimize.is_release() { - // FIXME: cargo bench/install do not accept `--release` - // and miri doesn't want it - match cmd_kind { - Kind::Bench | Kind::Install | Kind::Miri | Kind::MiriSetup | Kind::MiriTest => {} - _ => { - cargo.arg("--release"); - } - } + if self.config.rust_optimize.is_release() && + // cargo bench/install do not accept `--release` and miri doesn't want it + !matches!(cmd_kind, Kind::Bench | Kind::Install | Kind::Miri | Kind::MiriSetup | Kind::MiriTest) + { + cargo.arg("--release"); } // Remove make-related flags to ensure Cargo can correctly set things up diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 4d1aea3cd956a..25daeeb721eed 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -616,8 +616,6 @@ impl Config { }; // For the beta compiler, put special effort into ensuring the checksums are valid. - // FIXME: maybe we should do this for download-rustc as well? but it would be a pain to update - // this on each and every nightly ... let checksum = if should_verify { let error = format!( "src/stage0 doesn't contain a checksum for {url}. \ From 8d7c374b2ec4f6cfd99f9c8e7cd5b2624909fa21 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Aug 2024 09:10:00 +0300 Subject: [PATCH 054/685] improve rustup check in `x setup` Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/setup.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 8cd9ba5fd146f..f7b26712cabd6 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -247,9 +247,11 @@ pub struct Link; impl Step for Link { type Output = (); const DEFAULT: bool = true; + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.alias("link") } + fn make_run(run: RunConfig<'_>) { if run.builder.config.dry_run() { return; @@ -262,21 +264,30 @@ impl Step for Link { } fn run(self, builder: &Builder<'_>) -> Self::Output { let config = &builder.config; + if config.dry_run() { return; } + + if !rustup_installed(builder) { + println!("WARNING: `rustup` is not installed; Skipping `stage1` toolchain linking."); + return; + } + let stage_path = ["build", config.build.rustc_target_arg(), "stage1"].join(MAIN_SEPARATOR_STR); - if !rustup_installed(builder) { - eprintln!("`rustup` is not installed; cannot link `stage1` toolchain"); - } else if stage_dir_exists(&stage_path[..]) && !config.dry_run() { + + if stage_dir_exists(&stage_path[..]) && !config.dry_run() { attempt_toolchain_link(builder, &stage_path[..]); } } } fn rustup_installed(builder: &Builder<'_>) -> bool { - command("rustup").arg("--version").run_capture_stdout(builder).is_success() + let mut rustup = command("rustup"); + rustup.arg("--version"); + + rustup.allow_failure().run_always().run_capture_stdout(builder).is_success() } fn stage_dir_exists(stage_path: &str) -> bool { From 6899f5a8e12986ee16e028f1597963d0de668aca Mon Sep 17 00:00:00 2001 From: Mrmaxmeier Date: Tue, 6 Aug 2024 20:31:12 +0200 Subject: [PATCH 055/685] -Zembed-source: Don't try to warn about incompatible codegen backends --- compiler/rustc_session/messages.ftl | 2 -- compiler/rustc_session/src/errors.rs | 4 ---- compiler/rustc_session/src/session.rs | 7 ------- 3 files changed, 13 deletions(-) diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index afd5360c81194..01c371ee49884 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -18,8 +18,6 @@ session_embed_source_insufficient_dwarf_version = `-Zembed-source=y` requires at session_embed_source_requires_debug_info = `-Zembed-source=y` requires debug information to be enabled -session_embed_source_requires_llvm_backend = `-Zembed-source=y` is only supported on the LLVM codegen backend - session_expr_parentheses_needed = parentheses are required to parse this as an expression session_failed_to_create_profiler = failed to create profiler: {$err} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index f708109b87a0c..15bbd4ff7bf4b 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -175,10 +175,6 @@ pub(crate) struct EmbedSourceInsufficientDwarfVersion { #[diag(session_embed_source_requires_debug_info)] pub(crate) struct EmbedSourceRequiresDebugInfo; -#[derive(Diagnostic)] -#[diag(session_embed_source_requires_llvm_backend)] -pub(crate) struct EmbedSourceRequiresLLVMBackend; - #[derive(Diagnostic)] #[diag(session_target_stack_protector_not_supported)] pub(crate) struct StackProtectorNotSupportedForTarget<'a> { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 634f3684b51aa..e2ef144e732a4 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1305,9 +1305,6 @@ fn validate_commandline_args_with_session_available(sess: &Session) { let dwarf_version = sess.opts.unstable_opts.dwarf_version.unwrap_or(sess.target.default_dwarf_version); - let uses_llvm_backend = - matches!(sess.opts.unstable_opts.codegen_backend.as_deref(), None | Some("llvm")); - if dwarf_version < 5 { sess.dcx().emit_warn(errors::EmbedSourceInsufficientDwarfVersion { dwarf_version }); } @@ -1315,10 +1312,6 @@ fn validate_commandline_args_with_session_available(sess: &Session) { if sess.opts.debuginfo == DebugInfo::None { sess.dcx().emit_warn(errors::EmbedSourceRequiresDebugInfo); } - - if !uses_llvm_backend { - sess.dcx().emit_warn(errors::EmbedSourceRequiresLLVMBackend); - } } if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray { From 1b587a6e76200fdd8364ef910246efa11c973e7b Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 6 Aug 2024 14:33:14 -0700 Subject: [PATCH 056/685] alloc: add ToString specialization for `&&str` Fixes #128690 --- library/alloc/src/string.rs | 43 ++++++++++++++++---- tests/codegen/issues/str-to-string-128690.rs | 36 ++++++++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 tests/codegen/issues/str-to-string-128690.rs diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 124230812df56..d943901e9ecf9 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2643,14 +2643,41 @@ impl ToString for i8 { } } -#[doc(hidden)] -#[cfg(not(no_global_oom_handling))] -#[stable(feature = "str_to_string_specialization", since = "1.9.0")] -impl ToString for str { - #[inline] - fn to_string(&self) -> String { - String::from(self) - } +// Generic/generated code can sometimes have multiple, nested references +// for strings, including `&&&str`s that would never be written +// by hand. This macro generates twelve layers of nested `&`-impl +// for primitive strings. +macro_rules! to_string_str { + {type ; x $($x:ident)*} => { + &to_string_str! { type ; $($x)* } + }; + {type ;} => { str }; + {impl ; x $($x:ident)*} => { + to_string_str! { $($x)* } + }; + {impl ;} => { }; + {$self:expr ; x $($x:ident)*} => { + *(to_string_str! { $self ; $($x)* }) + }; + {$self:expr ;} => { $self }; + {$($x:ident)*} => { + #[doc(hidden)] + #[cfg(not(no_global_oom_handling))] + #[stable(feature = "str_to_string_specialization", since = "1.9.0")] + impl ToString for to_string_str!(type ; $($x)*) { + #[inline] + fn to_string(&self) -> String { + String::from(to_string_str!(self ; $($x)*)) + } + } + to_string_str! { impl ; $($x)* } + }; +} + +to_string_str! { + x x x x + x x x x + x x x x } #[doc(hidden)] diff --git a/tests/codegen/issues/str-to-string-128690.rs b/tests/codegen/issues/str-to-string-128690.rs new file mode 100644 index 0000000000000..8b416306ba66e --- /dev/null +++ b/tests/codegen/issues/str-to-string-128690.rs @@ -0,0 +1,36 @@ +//@ compile-flags: -C opt-level=3 -Z merge-functions=disabled +#![crate_type = "lib"] + +//! Make sure str::to_string is specialized not to use fmt machinery. + +// CHECK-LABEL: define {{(dso_local )?}}void @one_ref +#[no_mangle] +pub fn one_ref(input: &str) -> String { + // CHECK-NOT: {{(call|invoke).*}}fmt + input.to_string() +} + +// CHECK-LABEL: define {{(dso_local )?}}void @two_ref +#[no_mangle] +pub fn two_ref(input: &&str) -> String { + // CHECK-NOT: {{(call|invoke).*}}fmt + input.to_string() +} + +// CHECK-LABEL: define {{(dso_local )?}}void @thirteen_ref +#[no_mangle] +pub fn thirteen_ref(input: &&&&&&&&&&&&&str) -> String { + // CHECK-NOT: {{(call|invoke).*}}fmt + input.to_string() +} + +// This is a known performance cliff because of the macro-generated +// specialized impl. If this test suddenly starts failing, +// consider removing the `to_string_str!` macro in `alloc/str/string.rs`. +// +// CHECK-LABEL: define {{(dso_local )?}}void @fourteen_ref +#[no_mangle] +pub fn fourteen_ref(input: &&&&&&&&&&&&&&str) -> String { + // CHECK: {{(call|invoke).*}}fmt + input.to_string() +} From 20c833c632d76ee78284441226f12b919318bc4b Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 6 Aug 2024 18:18:15 -0700 Subject: [PATCH 057/685] diagnostics: `Box` suggestion with multiple matching impl The two altered expectation messages both seem like improvements: - `coerce-expect-unsized-ascribed.stderr` says you can go `Box -> Box`, which you can. - `upcast_soundness_bug.stderr` used to say that you could go `Box> -> Box`, which you can't, because the type parameters are missing in the destination and the only ones that work aren't what's needed. --- .../error_reporting/infer/note_and_explain.rs | 24 +++++++------------ .../coerce-expect-unsized-ascribed.stderr | 4 ++++ tests/ui/traits/upcast_soundness_bug.stderr | 1 - 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs index 864510bb65047..0c140f93d94b2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs @@ -313,11 +313,9 @@ impl Trait for X { (ty::Dynamic(t, _, ty::DynKind::Dyn), _) if let Some(def_id) = t.principal_def_id() => { - let mut impl_def_ids = vec![]; - tcx.for_each_relevant_impl(def_id, values.found, |did| { - impl_def_ids.push(did) - }); - if let [_] = &impl_def_ids[..] { + let has_non_blanket_impl = + tcx.non_blanket_impls_for_ty(def_id, values.found).next().is_some(); + if has_non_blanket_impl { let trait_name = tcx.item_name(def_id); diag.help(format!( "`{}` implements `{trait_name}` so you could box the found value \ @@ -330,11 +328,9 @@ impl Trait for X { (_, ty::Dynamic(t, _, ty::DynKind::Dyn)) if let Some(def_id) = t.principal_def_id() => { - let mut impl_def_ids = vec![]; - tcx.for_each_relevant_impl(def_id, values.expected, |did| { - impl_def_ids.push(did) - }); - if let [_] = &impl_def_ids[..] { + let has_non_blanket_impl = + tcx.non_blanket_impls_for_ty(def_id, values.expected).next().is_some(); + if has_non_blanket_impl { let trait_name = tcx.item_name(def_id); diag.help(format!( "`{}` implements `{trait_name}` so you could change the expected \ @@ -346,11 +342,9 @@ impl Trait for X { (ty::Dynamic(t, _, ty::DynKind::DynStar), _) if let Some(def_id) = t.principal_def_id() => { - let mut impl_def_ids = vec![]; - tcx.for_each_relevant_impl(def_id, values.found, |did| { - impl_def_ids.push(did) - }); - if let [_] = &impl_def_ids[..] { + let has_non_blanket_impl = + tcx.non_blanket_impls_for_ty(def_id, values.found).next().is_some(); + if has_non_blanket_impl { let trait_name = tcx.item_name(def_id); diag.help(format!( "`{}` implements `{trait_name}`, `#[feature(dyn_star)]` is likely \ diff --git a/tests/ui/coercion/coerce-expect-unsized-ascribed.stderr b/tests/ui/coercion/coerce-expect-unsized-ascribed.stderr index 646044ae41abc..0c220a13876b8 100644 --- a/tests/ui/coercion/coerce-expect-unsized-ascribed.stderr +++ b/tests/ui/coercion/coerce-expect-unsized-ascribed.stderr @@ -42,6 +42,7 @@ LL | let _ = type_ascribe!(Box::new( if true { false } else { true }), Box` found struct `Box` + = help: `bool` implements `Debug` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well error[E0308]: mismatched types --> $DIR/coerce-expect-unsized-ascribed.rs:16:27 @@ -51,6 +52,7 @@ LL | let _ = type_ascribe!(Box::new( match true { true => 'a', false => 'b' | = note: expected struct `Box` found struct `Box` + = help: `char` implements `Debug` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well error[E0308]: mismatched types --> $DIR/coerce-expect-unsized-ascribed.rs:18:27 @@ -96,6 +98,7 @@ LL | let _ = type_ascribe!(&if true { false } else { true }, &dyn Debug); | = note: expected reference `&dyn Debug` found reference `&bool` + = help: `bool` implements `Debug` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well error[E0308]: mismatched types --> $DIR/coerce-expect-unsized-ascribed.rs:24:27 @@ -105,6 +108,7 @@ LL | let _ = type_ascribe!(&match true { true => 'a', false => 'b' }, &dyn D | = note: expected reference `&dyn Debug` found reference `&char` + = help: `char` implements `Debug` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well error[E0308]: mismatched types --> $DIR/coerce-expect-unsized-ascribed.rs:26:27 diff --git a/tests/ui/traits/upcast_soundness_bug.stderr b/tests/ui/traits/upcast_soundness_bug.stderr index 5864abcdb41f5..31f77b52b5fbf 100644 --- a/tests/ui/traits/upcast_soundness_bug.stderr +++ b/tests/ui/traits/upcast_soundness_bug.stderr @@ -6,7 +6,6 @@ LL | let p = p as *const dyn Trait; // <- this is bad! | = note: expected trait object `dyn Trait` found trait object `dyn Trait` - = help: `dyn Trait` implements `Trait` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well error: aborting due to 1 previous error From 0475bddae2905def7d61f305bd53367367536dd1 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 7 Aug 2024 04:57:07 +0000 Subject: [PATCH 058/685] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f6fbc9844f860..699a17a4943aa 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -c9687a95a602091777e28703aa5abf20f1ce1797 +6696447f784a888446d13bb400a8d507a68331c9 From de9b5c3ea280112169887065354706e102e6290d Mon Sep 17 00:00:00 2001 From: carbotaniuman <41451839+carbotaniuman@users.noreply.github.com> Date: Wed, 7 Aug 2024 01:36:28 -0500 Subject: [PATCH 059/685] Stabilize `unsafe_attributes` --- .../rustc_ast_passes/src/ast_validation.rs | 2 +- compiler/rustc_ast_passes/src/feature_gate.rs | 1 - .../src/cfg_accessible.rs | 1 - compiler/rustc_builtin_macros/src/derive.rs | 1 - compiler/rustc_builtin_macros/src/util.rs | 1 - compiler/rustc_expand/src/config.rs | 13 +----- compiler/rustc_expand/src/expand.rs | 2 +- compiler/rustc_feature/src/accepted.rs | 2 + compiler/rustc_feature/src/unstable.rs | 2 - compiler/rustc_lint_defs/src/builtin.rs | 1 - compiler/rustc_parse/src/parser/attr.rs | 4 +- compiler/rustc_parse/src/validate_attr.rs | 40 ++++++------------- .../rustfmt/tests/target/unsafe_attributes.rs | 1 - .../unsafe/cfg-unsafe-attributes.rs | 1 - .../unsafe/derive-unsafe-attributes.rs | 2 - .../unsafe/derive-unsafe-attributes.stderr | 14 +++---- .../unsafe/double-unsafe-attributes.rs | 2 - .../unsafe/double-unsafe-attributes.stderr | 6 +-- .../unsafe/extraneous-unsafe-attributes.rs | 1 - .../extraneous-unsafe-attributes.stderr | 16 ++++---- .../unsafe/proc-unsafe-attributes.rs | 2 - .../unsafe/proc-unsafe-attributes.stderr | 30 +++++++------- .../ui/attributes/unsafe/unsafe-attributes.rs | 1 - .../unsafe/unsafe-safe-attribute.rs | 2 - .../unsafe/unsafe-safe-attribute.stderr | 2 +- .../unsafe-safe-attribute_diagnostic.rs | 2 - .../unsafe-safe-attribute_diagnostic.stderr | 2 +- .../feature-gate-unsafe-attributes.rs | 8 ---- .../feature-gate-unsafe-attributes.stderr | 13 ------ .../in_2024_compatibility.rs | 1 - .../in_2024_compatibility.stderr | 2 +- .../unsafe-attribute-marked.rs | 1 - .../unsafe-attributes-fix.fixed | 1 - .../unsafe-attributes-fix.rs | 1 - .../unsafe-attributes-fix.stderr | 14 +++---- .../unsafe-attributes.edition2024.stderr | 2 +- .../unsafe-attributes/unsafe-attributes.rs | 1 - 37 files changed, 64 insertions(+), 134 deletions(-) delete mode 100644 tests/ui/feature-gates/feature-gate-unsafe-attributes.rs delete mode 100644 tests/ui/feature-gates/feature-gate-unsafe-attributes.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index a353c79f12d45..837cb805700d2 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -887,7 +887,7 @@ fn validate_generic_param_order(dcx: DiagCtxtHandle<'_>, generics: &[GenericPara impl<'a> Visitor<'a> for AstValidator<'a> { fn visit_attribute(&mut self, attr: &Attribute) { - validate_attr::check_attr(&self.features, &self.session.psess, attr); + validate_attr::check_attr(&self.session.psess, attr); } fn visit_ty(&mut self, ty: &'a Ty) { diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 3ceb8e0711a21..214a37bca03e2 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -559,7 +559,6 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(mut_ref, "mutable by-reference bindings are experimental"); gate_all!(precise_capturing, "precise captures on `impl Trait` are experimental"); gate_all!(global_registration, "global registration is experimental"); - gate_all!(unsafe_attributes, "`#[unsafe()]` markers for attributes are experimental"); gate_all!(return_type_notation, "return type notation is experimental"); if !visitor.features.never_patterns { diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs index 006b6aa823fbf..3d3bd3aea059c 100644 --- a/compiler/rustc_builtin_macros/src/cfg_accessible.rs +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -47,7 +47,6 @@ impl MultiItemModifier for Expander { ) -> ExpandResult, Annotatable> { let template = AttributeTemplate { list: Some("path"), ..Default::default() }; validate_attr::check_builtin_meta_item( - &ecx.ecfg.features, &ecx.sess.psess, meta_item, ast::AttrStyle::Outer, diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index 57bddf0ab60ad..e8704bc2f639d 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -38,7 +38,6 @@ impl MultiItemModifier for Expander { let template = AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; validate_attr::check_builtin_meta_item( - features, &sess.psess, meta_item, ast::AttrStyle::Outer, diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 73cc8ff547d51..0bcd5aef28bab 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -17,7 +17,6 @@ pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaI // All the built-in macro attributes are "words" at the moment. let template = AttributeTemplate { word: true, ..Default::default() }; validate_attr::check_builtin_meta_item( - &ecx.ecfg.features, &ecx.sess.psess, meta_item, AttrStyle::Outer, diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index f6bf9f5e89f2f..b0d3fecbb479b 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -265,12 +265,7 @@ impl<'a> StripUnconfigured<'a> { /// is in the original source file. Gives a compiler error if the syntax of /// the attribute is incorrect. pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec { - validate_attr::check_attribute_safety( - self.features.unwrap_or(&Features::default()), - &self.sess.psess, - AttributeSafety::Normal, - &cfg_attr, - ); + validate_attr::check_attribute_safety(&self.sess.psess, AttributeSafety::Normal, &cfg_attr); let Some((cfg_predicate, expanded_attrs)) = rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess) @@ -395,11 +390,7 @@ impl<'a> StripUnconfigured<'a> { } }; - validate_attr::deny_builtin_meta_unsafety( - self.features.unwrap_or(&Features::default()), - &self.sess.psess, - &meta_item, - ); + validate_attr::deny_builtin_meta_unsafety(&self.sess.psess, &meta_item); ( parse_cfg(&meta_item, self.sess).map_or(true, |meta_item| { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index d8cb367e3face..61b36e15487e0 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1883,7 +1883,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { let mut span: Option = None; while let Some(attr) = attrs.next() { rustc_ast_passes::feature_gate::check_attribute(attr, self.cx.sess, features); - validate_attr::check_attr(features, &self.cx.sess.psess, attr); + validate_attr::check_attr(&self.cx.sess.psess, attr); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; span = Some(current_span); diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index e42a655531b5d..689e2d2771e6f 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -388,6 +388,8 @@ declare_features! ( (accepted, universal_impl_trait, "1.26.0", Some(34511)), /// Allows arbitrary delimited token streams in non-macro attributes. (accepted, unrestricted_attribute_tokens, "1.34.0", Some(55208)), + /// Allows unsafe attributes. + (accepted, unsafe_attributes, "CURRENT_RUSTC_VERSION", Some(123757)), /// The `unsafe_op_in_unsafe_fn` lint (allowed by default): no longer treat an unsafe function as an unsafe block. (accepted, unsafe_block_in_unsafe_fn, "1.52.0", Some(71668)), /// Allows unsafe on extern declarations and safety qualifiers over internal items. diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 88a4b5a838246..a9e9bdd6a4190 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -627,8 +627,6 @@ declare_features! ( (unstable, type_changing_struct_update, "1.58.0", Some(86555)), /// Allows unnamed fields of struct and union type (incomplete, unnamed_fields, "1.74.0", Some(49804)), - /// Allows unsafe attributes. - (unstable, unsafe_attributes, "1.80.0", Some(123757)), /// Allows const generic parameters to be defined with types that /// are not `Sized`, e.g. `fn foo() {`. (incomplete, unsized_const_params, "CURRENT_RUSTC_VERSION", Some(95174)), diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index ff0bdfcc9d261..aa281a6f67b45 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -4937,7 +4937,6 @@ declare_lint! { /// ### Example /// /// ```rust - /// #![feature(unsafe_attributes)] /// #![warn(unsafe_attr_outside_unsafe)] /// /// #[no_mangle] diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 8fdfbcee38546..4fea00edebc67 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -4,7 +4,7 @@ use rustc_ast::token::{self, Delimiter}; use rustc_errors::codes::*; use rustc_errors::{Diag, PResult}; use rustc_span::symbol::kw; -use rustc_span::{sym, BytePos, Span}; +use rustc_span::{BytePos, Span}; use thin_vec::ThinVec; use tracing::debug; @@ -261,7 +261,6 @@ impl<'a> Parser<'a> { let is_unsafe = this.eat_keyword(kw::Unsafe); let unsafety = if is_unsafe { let unsafe_span = this.prev_token.span; - this.psess.gated_spans.gate(sym::unsafe_attributes, unsafe_span); this.expect(&token::OpenDelim(Delimiter::Parenthesis))?; ast::Safety::Unsafe(unsafe_span) } else { @@ -400,7 +399,6 @@ impl<'a> Parser<'a> { }; let unsafety = if is_unsafe { let unsafe_span = self.prev_token.span; - self.psess.gated_spans.gate(sym::unsafe_attributes, unsafe_span); self.expect(&token::OpenDelim(Delimiter::Parenthesis))?; ast::Safety::Unsafe(unsafe_span) diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs index a64c00f3b6cbc..fce41bd90be7b 100644 --- a/compiler/rustc_parse/src/validate_attr.rs +++ b/compiler/rustc_parse/src/validate_attr.rs @@ -7,9 +7,7 @@ use rustc_ast::{ NestedMetaItem, Safety, }; use rustc_errors::{Applicability, FatalError, PResult}; -use rustc_feature::{ - AttributeSafety, AttributeTemplate, BuiltinAttribute, Features, BUILTIN_ATTRIBUTE_MAP, -}; +use rustc_feature::{AttributeSafety, AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; use rustc_session::errors::report_lit_error; use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE}; use rustc_session::lint::BuiltinLintDiag; @@ -18,7 +16,7 @@ use rustc_span::{sym, BytePos, Span, Symbol}; use crate::{errors, parse_in}; -pub fn check_attr(features: &Features, psess: &ParseSess, attr: &Attribute) { +pub fn check_attr(psess: &ParseSess, attr: &Attribute) { if attr.is_doc_comment() { return; } @@ -28,7 +26,7 @@ pub fn check_attr(features: &Features, psess: &ParseSess, attr: &Attribute) { // All non-builtin attributes are considered safe let safety = attr_info.map(|x| x.safety).unwrap_or(AttributeSafety::Normal); - check_attribute_safety(features, psess, safety, attr); + check_attribute_safety(psess, safety, attr); // Check input tokens for built-in and key-value attributes. match attr_info { @@ -36,9 +34,9 @@ pub fn check_attr(features: &Features, psess: &ParseSess, attr: &Attribute) { Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => { match parse_meta(psess, attr) { // Don't check safety again, we just did that - Ok(meta) => check_builtin_meta_item( - features, psess, &meta, attr.style, *name, *template, false, - ), + Ok(meta) => { + check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false) + } Err(err) => { err.emit(); } @@ -157,16 +155,7 @@ fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaIte } } -pub fn check_attribute_safety( - features: &Features, - psess: &ParseSess, - safety: AttributeSafety, - attr: &Attribute, -) { - if !features.unsafe_attributes { - return; - } - +pub fn check_attribute_safety(psess: &ParseSess, safety: AttributeSafety, attr: &Attribute) { let attr_item = attr.get_normal_item(); if safety == AttributeSafety::Unsafe { @@ -215,21 +204,18 @@ pub fn check_attribute_safety( // Called by `check_builtin_meta_item` and code that manually denies // `unsafe(...)` in `cfg` -pub fn deny_builtin_meta_unsafety(features: &Features, psess: &ParseSess, meta: &MetaItem) { +pub fn deny_builtin_meta_unsafety(psess: &ParseSess, meta: &MetaItem) { // This only supports denying unsafety right now - making builtin attributes // support unsafety will requite us to thread the actual `Attribute` through // for the nice diagnostics. - if features.unsafe_attributes { - if let Safety::Unsafe(unsafe_span) = meta.unsafety { - psess - .dcx() - .emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() }); - } + if let Safety::Unsafe(unsafe_span) = meta.unsafety { + psess + .dcx() + .emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() }); } } pub fn check_builtin_meta_item( - features: &Features, psess: &ParseSess, meta: &MetaItem, style: ast::AttrStyle, @@ -246,7 +232,7 @@ pub fn check_builtin_meta_item( } if deny_unsafety { - deny_builtin_meta_unsafety(features, psess, meta); + deny_builtin_meta_unsafety(psess, meta); } } diff --git a/src/tools/rustfmt/tests/target/unsafe_attributes.rs b/src/tools/rustfmt/tests/target/unsafe_attributes.rs index a05bedc751ae2..d79c56f21479f 100644 --- a/src/tools/rustfmt/tests/target/unsafe_attributes.rs +++ b/src/tools/rustfmt/tests/target/unsafe_attributes.rs @@ -1,4 +1,3 @@ -#![feature(unsafe_attributes)] // https://github.com/rust-lang/rust/issues/123757 // #![simple_ident] diff --git a/tests/ui/attributes/unsafe/cfg-unsafe-attributes.rs b/tests/ui/attributes/unsafe/cfg-unsafe-attributes.rs index ce365d1a8b1c8..6a9853b2f6fc6 100644 --- a/tests/ui/attributes/unsafe/cfg-unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/cfg-unsafe-attributes.rs @@ -1,5 +1,4 @@ //@ build-pass -#![feature(unsafe_attributes)] #[cfg_attr(all(), unsafe(no_mangle))] fn a() {} diff --git a/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs b/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs index b8edb4aab907b..95fc19f506b24 100644 --- a/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs @@ -1,5 +1,3 @@ -#![feature(unsafe_attributes)] - #[derive(unsafe(Debug))] //~^ ERROR: expected identifier, found keyword `unsafe` //~| ERROR: traits in `#[derive(...)]` don't accept arguments diff --git a/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr index c40a5512fd5cd..4002c930b63e5 100644 --- a/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr +++ b/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr @@ -1,5 +1,5 @@ error: expected identifier, found keyword `unsafe` - --> $DIR/derive-unsafe-attributes.rs:3:10 + --> $DIR/derive-unsafe-attributes.rs:1:10 | LL | #[derive(unsafe(Debug))] | ^^^^^^ expected identifier, found keyword @@ -10,13 +10,13 @@ LL | #[derive(r#unsafe(Debug))] | ++ error: traits in `#[derive(...)]` don't accept arguments - --> $DIR/derive-unsafe-attributes.rs:3:16 + --> $DIR/derive-unsafe-attributes.rs:1:16 | LL | #[derive(unsafe(Debug))] | ^^^^^^^ help: remove the arguments error: `derive` is not an unsafe attribute - --> $DIR/derive-unsafe-attributes.rs:12:3 + --> $DIR/derive-unsafe-attributes.rs:10:3 | LL | #[unsafe(derive(Debug))] | ^^^^^^ this is not an unsafe attribute @@ -24,7 +24,7 @@ LL | #[unsafe(derive(Debug))] = note: extraneous unsafe is not allowed in attributes error: expected identifier, found keyword `unsafe` - --> $DIR/derive-unsafe-attributes.rs:3:10 + --> $DIR/derive-unsafe-attributes.rs:1:10 | LL | #[derive(unsafe(Debug))] | ^^^^^^ expected identifier, found keyword @@ -36,7 +36,7 @@ LL | #[derive(r#unsafe(Debug))] | ++ error: expected identifier, found keyword `unsafe` - --> $DIR/derive-unsafe-attributes.rs:3:10 + --> $DIR/derive-unsafe-attributes.rs:1:10 | LL | #[derive(unsafe(Debug))] | ^^^^^^ expected identifier, found keyword @@ -48,13 +48,13 @@ LL | #[derive(r#unsafe(Debug))] | ++ error: cannot find derive macro `r#unsafe` in this scope - --> $DIR/derive-unsafe-attributes.rs:3:10 + --> $DIR/derive-unsafe-attributes.rs:1:10 | LL | #[derive(unsafe(Debug))] | ^^^^^^ error: cannot find derive macro `r#unsafe` in this scope - --> $DIR/derive-unsafe-attributes.rs:3:10 + --> $DIR/derive-unsafe-attributes.rs:1:10 | LL | #[derive(unsafe(Debug))] | ^^^^^^ diff --git a/tests/ui/attributes/unsafe/double-unsafe-attributes.rs b/tests/ui/attributes/unsafe/double-unsafe-attributes.rs index a6c0ea578f25a..894d1327da799 100644 --- a/tests/ui/attributes/unsafe/double-unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/double-unsafe-attributes.rs @@ -1,5 +1,3 @@ -#![feature(unsafe_attributes)] - #[unsafe(unsafe(no_mangle))] //~^ ERROR expected identifier, found keyword `unsafe` //~| ERROR cannot find attribute `r#unsafe` in this scope diff --git a/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr index 950b2636993c1..0825cf794083d 100644 --- a/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr +++ b/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr @@ -1,5 +1,5 @@ error: expected identifier, found keyword `unsafe` - --> $DIR/double-unsafe-attributes.rs:3:10 + --> $DIR/double-unsafe-attributes.rs:1:10 | LL | #[unsafe(unsafe(no_mangle))] | ^^^^^^ expected identifier, found keyword @@ -10,7 +10,7 @@ LL | #[unsafe(r#unsafe(no_mangle))] | ++ error: `r#unsafe` is not an unsafe attribute - --> $DIR/double-unsafe-attributes.rs:3:3 + --> $DIR/double-unsafe-attributes.rs:1:3 | LL | #[unsafe(unsafe(no_mangle))] | ^^^^^^ this is not an unsafe attribute @@ -18,7 +18,7 @@ LL | #[unsafe(unsafe(no_mangle))] = note: extraneous unsafe is not allowed in attributes error: cannot find attribute `r#unsafe` in this scope - --> $DIR/double-unsafe-attributes.rs:3:10 + --> $DIR/double-unsafe-attributes.rs:1:10 | LL | #[unsafe(unsafe(no_mangle))] | ^^^^^^ diff --git a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs index 0181add843bcd..b561550c19843 100644 --- a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs @@ -1,6 +1,5 @@ //@ edition: 2024 //@ compile-flags: -Zunstable-options -#![feature(unsafe_attributes)] #[unsafe(cfg(any()))] //~ ERROR: is not an unsafe attribute fn a() {} diff --git a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr index f39074b613d40..9fb7f062b912b 100644 --- a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr +++ b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr @@ -1,5 +1,5 @@ error: `cfg` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:5:3 + --> $DIR/extraneous-unsafe-attributes.rs:4:3 | LL | #[unsafe(cfg(any()))] | ^^^^^^ this is not an unsafe attribute @@ -7,7 +7,7 @@ LL | #[unsafe(cfg(any()))] = note: extraneous unsafe is not allowed in attributes error: `cfg_attr` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:8:3 + --> $DIR/extraneous-unsafe-attributes.rs:7:3 | LL | #[unsafe(cfg_attr(any(), allow(dead_code)))] | ^^^^^^ this is not an unsafe attribute @@ -15,7 +15,7 @@ LL | #[unsafe(cfg_attr(any(), allow(dead_code)))] = note: extraneous unsafe is not allowed in attributes error: `test` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:11:3 + --> $DIR/extraneous-unsafe-attributes.rs:10:3 | LL | #[unsafe(test)] | ^^^^^^ this is not an unsafe attribute @@ -23,7 +23,7 @@ LL | #[unsafe(test)] = note: extraneous unsafe is not allowed in attributes error: `ignore` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:14:3 + --> $DIR/extraneous-unsafe-attributes.rs:13:3 | LL | #[unsafe(ignore = "test")] | ^^^^^^ this is not an unsafe attribute @@ -31,7 +31,7 @@ LL | #[unsafe(ignore = "test")] = note: extraneous unsafe is not allowed in attributes error: `should_panic` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:17:3 + --> $DIR/extraneous-unsafe-attributes.rs:16:3 | LL | #[unsafe(should_panic(expected = "test"))] | ^^^^^^ this is not an unsafe attribute @@ -39,7 +39,7 @@ LL | #[unsafe(should_panic(expected = "test"))] = note: extraneous unsafe is not allowed in attributes error: `macro_use` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:20:3 + --> $DIR/extraneous-unsafe-attributes.rs:19:3 | LL | #[unsafe(macro_use)] | ^^^^^^ this is not an unsafe attribute @@ -47,7 +47,7 @@ LL | #[unsafe(macro_use)] = note: extraneous unsafe is not allowed in attributes error: `macro_export` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:22:7 + --> $DIR/extraneous-unsafe-attributes.rs:21:7 | LL | #[unsafe(macro_export)] | ^^^^^^ this is not an unsafe attribute @@ -55,7 +55,7 @@ LL | #[unsafe(macro_export)] = note: extraneous unsafe is not allowed in attributes error: `used` is not an unsafe attribute - --> $DIR/extraneous-unsafe-attributes.rs:28:3 + --> $DIR/extraneous-unsafe-attributes.rs:27:3 | LL | #[unsafe(used)] | ^^^^^^ this is not an unsafe attribute diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs index f29a5b3252b0a..eaf8706369a5a 100644 --- a/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs @@ -1,5 +1,3 @@ -#![feature(unsafe_attributes)] - #[unsafe(proc_macro)] //~^ ERROR: is not an unsafe attribute //~| ERROR attribute is only usable with crates of the `proc-macro` crate type diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr index 79d34d458bd6c..9c5751c82e4c4 100644 --- a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr +++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr @@ -1,11 +1,11 @@ error[E0452]: malformed lint attribute input - --> $DIR/proc-unsafe-attributes.rs:28:16 + --> $DIR/proc-unsafe-attributes.rs:26:16 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^^^^^^^^^^^^ bad attribute argument error[E0452]: malformed lint attribute input - --> $DIR/proc-unsafe-attributes.rs:28:16 + --> $DIR/proc-unsafe-attributes.rs:26:16 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^^^^^^^^^^^^ bad attribute argument @@ -13,7 +13,7 @@ LL | #[unsafe(allow(unsafe(dead_code)))] = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: `proc_macro` is not an unsafe attribute - --> $DIR/proc-unsafe-attributes.rs:3:3 + --> $DIR/proc-unsafe-attributes.rs:1:3 | LL | #[unsafe(proc_macro)] | ^^^^^^ this is not an unsafe attribute @@ -21,7 +21,7 @@ LL | #[unsafe(proc_macro)] = note: extraneous unsafe is not allowed in attributes error: `proc_macro_derive` is not an unsafe attribute - --> $DIR/proc-unsafe-attributes.rs:9:3 + --> $DIR/proc-unsafe-attributes.rs:7:3 | LL | #[unsafe(proc_macro_derive(Foo))] | ^^^^^^ this is not an unsafe attribute @@ -29,7 +29,7 @@ LL | #[unsafe(proc_macro_derive(Foo))] = note: extraneous unsafe is not allowed in attributes error: expected identifier, found keyword `unsafe` - --> $DIR/proc-unsafe-attributes.rs:14:21 + --> $DIR/proc-unsafe-attributes.rs:12:21 | LL | #[proc_macro_derive(unsafe(Foo))] | ^^^^^^ expected identifier, found keyword @@ -40,7 +40,7 @@ LL | #[proc_macro_derive(r#unsafe(Foo))] | ++ error: `proc_macro_attribute` is not an unsafe attribute - --> $DIR/proc-unsafe-attributes.rs:19:3 + --> $DIR/proc-unsafe-attributes.rs:17:3 | LL | #[unsafe(proc_macro_attribute)] | ^^^^^^ this is not an unsafe attribute @@ -48,7 +48,7 @@ LL | #[unsafe(proc_macro_attribute)] = note: extraneous unsafe is not allowed in attributes error: `allow` is not an unsafe attribute - --> $DIR/proc-unsafe-attributes.rs:24:3 + --> $DIR/proc-unsafe-attributes.rs:22:3 | LL | #[unsafe(allow(dead_code))] | ^^^^^^ this is not an unsafe attribute @@ -56,7 +56,7 @@ LL | #[unsafe(allow(dead_code))] = note: extraneous unsafe is not allowed in attributes error: `allow` is not an unsafe attribute - --> $DIR/proc-unsafe-attributes.rs:28:3 + --> $DIR/proc-unsafe-attributes.rs:26:3 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^ this is not an unsafe attribute @@ -64,7 +64,7 @@ LL | #[unsafe(allow(unsafe(dead_code)))] = note: extraneous unsafe is not allowed in attributes error: expected identifier, found keyword `unsafe` - --> $DIR/proc-unsafe-attributes.rs:28:16 + --> $DIR/proc-unsafe-attributes.rs:26:16 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^ expected identifier, found keyword @@ -75,31 +75,31 @@ LL | #[unsafe(allow(r#unsafe(dead_code)))] | ++ error: the `#[proc_macro]` attribute is only usable with crates of the `proc-macro` crate type - --> $DIR/proc-unsafe-attributes.rs:3:1 + --> $DIR/proc-unsafe-attributes.rs:1:1 | LL | #[unsafe(proc_macro)] | ^^^^^^^^^^^^^^^^^^^^^ error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type - --> $DIR/proc-unsafe-attributes.rs:9:1 + --> $DIR/proc-unsafe-attributes.rs:7:1 | LL | #[unsafe(proc_macro_derive(Foo))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type - --> $DIR/proc-unsafe-attributes.rs:14:1 + --> $DIR/proc-unsafe-attributes.rs:12:1 | LL | #[proc_macro_derive(unsafe(Foo))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: the `#[proc_macro_attribute]` attribute is only usable with crates of the `proc-macro` crate type - --> $DIR/proc-unsafe-attributes.rs:19:1 + --> $DIR/proc-unsafe-attributes.rs:17:1 | LL | #[unsafe(proc_macro_attribute)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0452]: malformed lint attribute input - --> $DIR/proc-unsafe-attributes.rs:28:16 + --> $DIR/proc-unsafe-attributes.rs:26:16 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^^^^^^^^^^^^ bad attribute argument @@ -107,7 +107,7 @@ LL | #[unsafe(allow(unsafe(dead_code)))] = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0452]: malformed lint attribute input - --> $DIR/proc-unsafe-attributes.rs:28:16 + --> $DIR/proc-unsafe-attributes.rs:26:16 | LL | #[unsafe(allow(unsafe(dead_code)))] | ^^^^^^^^^^^^^^^^^ bad attribute argument diff --git a/tests/ui/attributes/unsafe/unsafe-attributes.rs b/tests/ui/attributes/unsafe/unsafe-attributes.rs index 33a412add500c..5c57767b3b964 100644 --- a/tests/ui/attributes/unsafe/unsafe-attributes.rs +++ b/tests/ui/attributes/unsafe/unsafe-attributes.rs @@ -1,5 +1,4 @@ //@ build-pass -#![feature(unsafe_attributes)] #[unsafe(no_mangle)] fn a() {} diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute.rs b/tests/ui/attributes/unsafe/unsafe-safe-attribute.rs index 67db36afd2e69..5af03a2b8d137 100644 --- a/tests/ui/attributes/unsafe/unsafe-safe-attribute.rs +++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute.rs @@ -1,5 +1,3 @@ -#![feature(unsafe_attributes)] - #[unsafe(repr(C))] //~ ERROR: is not an unsafe attribute struct Foo {} diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr b/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr index 584b0ea797d0a..55172c91aaeef 100644 --- a/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr +++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr @@ -1,5 +1,5 @@ error: `repr` is not an unsafe attribute - --> $DIR/unsafe-safe-attribute.rs:3:3 + --> $DIR/unsafe-safe-attribute.rs:1:3 | LL | #[unsafe(repr(C))] | ^^^^^^ this is not an unsafe attribute diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.rs b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.rs index ff2eb61b40538..0f241cc439f34 100644 --- a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.rs +++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.rs @@ -1,5 +1,3 @@ -#![feature(unsafe_attributes)] - #[unsafe(diagnostic::on_unimplemented( //~ ERROR: is not an unsafe attribute message = "testing", ))] diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr index 26b5e4e37b931..3bc291db5acf8 100644 --- a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr +++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr @@ -1,5 +1,5 @@ error: `diagnostic::on_unimplemented` is not an unsafe attribute - --> $DIR/unsafe-safe-attribute_diagnostic.rs:3:3 + --> $DIR/unsafe-safe-attribute_diagnostic.rs:1:3 | LL | #[unsafe(diagnostic::on_unimplemented( | ^^^^^^ this is not an unsafe attribute diff --git a/tests/ui/feature-gates/feature-gate-unsafe-attributes.rs b/tests/ui/feature-gates/feature-gate-unsafe-attributes.rs deleted file mode 100644 index 9eba415dda0e5..0000000000000 --- a/tests/ui/feature-gates/feature-gate-unsafe-attributes.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[unsafe(no_mangle)] //~ ERROR [E0658] -extern "C" fn foo() { - -} - -fn main() { - foo(); -} diff --git a/tests/ui/feature-gates/feature-gate-unsafe-attributes.stderr b/tests/ui/feature-gates/feature-gate-unsafe-attributes.stderr deleted file mode 100644 index dfcea756b02b6..0000000000000 --- a/tests/ui/feature-gates/feature-gate-unsafe-attributes.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0658]: `#[unsafe()]` markers for attributes are experimental - --> $DIR/feature-gate-unsafe-attributes.rs:1:3 - | -LL | #[unsafe(no_mangle)] - | ^^^^^^ - | - = note: see issue #123757 for more information - = help: add `#![feature(unsafe_attributes)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.rs b/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.rs index c6f9115cde750..f3b8645abaf89 100644 --- a/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.rs +++ b/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.rs @@ -1,5 +1,4 @@ #![deny(rust_2024_compatibility)] -#![feature(unsafe_attributes)] #[no_mangle] //~^ ERROR: unsafe attribute used without unsafe diff --git a/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.stderr b/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.stderr index f0689d9883c9e..4629a154ac3ce 100644 --- a/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.stderr +++ b/tests/ui/rust-2024/unsafe-attributes/in_2024_compatibility.stderr @@ -1,5 +1,5 @@ error: unsafe attribute used without unsafe - --> $DIR/in_2024_compatibility.rs:4:3 + --> $DIR/in_2024_compatibility.rs:3:3 | LL | #[no_mangle] | ^^^^^^^^^ usage of unsafe attribute diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attribute-marked.rs b/tests/ui/rust-2024/unsafe-attributes/unsafe-attribute-marked.rs index 279ced2525a23..7c919fed976f5 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attribute-marked.rs +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attribute-marked.rs @@ -4,7 +4,6 @@ //@[edition2024] compile-flags: -Zunstable-options //@ check-pass -#![feature(unsafe_attributes)] #[unsafe(no_mangle)] extern "C" fn foo() {} diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.fixed b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.fixed index 6ebdff0334c8b..586881d180767 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.fixed +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.fixed @@ -1,5 +1,4 @@ //@ run-rustfix -#![feature(unsafe_attributes)] #![deny(unsafe_attr_outside_unsafe)] macro_rules! tt { diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.rs b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.rs index c78ff45ea4cdf..03e122c7d57e7 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.rs +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.rs @@ -1,5 +1,4 @@ //@ run-rustfix -#![feature(unsafe_attributes)] #![deny(unsafe_attr_outside_unsafe)] macro_rules! tt { diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.stderr b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.stderr index c95984f58ecf7..64debc58905c3 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.stderr +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes-fix.stderr @@ -1,5 +1,5 @@ error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:44:6 + --> $DIR/unsafe-attributes-fix.rs:43:6 | LL | tt!([no_mangle]); | ^^^^^^^^^ usage of unsafe attribute @@ -7,7 +7,7 @@ LL | tt!([no_mangle]); = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! = note: for more information, see issue #123757 note: the lint level is defined here - --> $DIR/unsafe-attributes-fix.rs:3:9 + --> $DIR/unsafe-attributes-fix.rs:2:9 | LL | #![deny(unsafe_attr_outside_unsafe)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | tt!([unsafe(no_mangle)]); | +++++++ + error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:14:11 + --> $DIR/unsafe-attributes-fix.rs:13:11 | LL | #[$e] | ^^ usage of unsafe attribute @@ -34,7 +34,7 @@ LL | #[unsafe($e)] | +++++++ + error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:48:7 + --> $DIR/unsafe-attributes-fix.rs:47:7 | LL | meta!(no_mangle); | ^^^^^^^^^ usage of unsafe attribute @@ -47,7 +47,7 @@ LL | meta!(unsafe(no_mangle)); | +++++++ + error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:51:8 + --> $DIR/unsafe-attributes-fix.rs:50:8 | LL | meta2!(export_name = "baw"); | ^^^^^^^^^^^ usage of unsafe attribute @@ -60,7 +60,7 @@ LL | meta2!(unsafe(export_name = "baw")); | +++++++ + error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:23:11 + --> $DIR/unsafe-attributes-fix.rs:22:11 | LL | #[$e = $l] | ^^ usage of unsafe attribute @@ -77,7 +77,7 @@ LL | #[unsafe($e = $l)] | +++++++ + error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes-fix.rs:56:3 + --> $DIR/unsafe-attributes-fix.rs:55:3 | LL | #[no_mangle] | ^^^^^^^^^ usage of unsafe attribute diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.edition2024.stderr b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.edition2024.stderr index 35475d6671626..fb697e14ef1c7 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.edition2024.stderr +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.edition2024.stderr @@ -1,5 +1,5 @@ error: unsafe attribute used without unsafe - --> $DIR/unsafe-attributes.rs:9:3 + --> $DIR/unsafe-attributes.rs:8:3 | LL | #[no_mangle] | ^^^^^^^^^ usage of unsafe attribute diff --git a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.rs b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.rs index 3a6af9dfb2b0f..f6f2994bb6def 100644 --- a/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.rs +++ b/tests/ui/rust-2024/unsafe-attributes/unsafe-attributes.rs @@ -4,7 +4,6 @@ //@[edition2024] edition:2024 //@[edition2024] compile-flags: -Zunstable-options -#![feature(unsafe_attributes)] #[no_mangle] //[edition2024]~ ERROR: unsafe attribute used without unsafe extern "C" fn foo() {} From deeccf9d3e951459169bbfe6f0f36bc6f74da293 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 13 Jul 2024 18:47:44 +0200 Subject: [PATCH 060/685] remove some SSE/SSE2 intrinsics that are no longer used by stdarch --- src/tools/miri/src/shims/x86/avx.rs | 5 +-- src/tools/miri/src/shims/x86/mod.rs | 20 +--------- src/tools/miri/src/shims/x86/sse.rs | 17 +++----- src/tools/miri/src/shims/x86/sse2.rs | 40 ------------------- .../intrinsics/intrinsic_target_feature.rs | 6 +-- 5 files changed, 11 insertions(+), 77 deletions(-) diff --git a/src/tools/miri/src/shims/x86/avx.rs b/src/tools/miri/src/shims/x86/avx.rs index f36bb4826e487..2f6569e1823de 100644 --- a/src/tools/miri/src/shims/x86/avx.rs +++ b/src/tools/miri/src/shims/x86/avx.rs @@ -73,13 +73,12 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { round_all::(this, op, rounding, dest)?; } - // Used to implement _mm256_{sqrt,rcp,rsqrt}_ps functions. + // Used to implement _mm256_{rcp,rsqrt}_ps functions. // Performs the operations on all components of `op`. - "sqrt.ps.256" | "rcp.ps.256" | "rsqrt.ps.256" => { + "rcp.ps.256" | "rsqrt.ps.256" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let which = match unprefixed_name { - "sqrt.ps.256" => FloatUnaryOp::Sqrt, "rcp.ps.256" => FloatUnaryOp::Rcp, "rsqrt.ps.256" => FloatUnaryOp::Rsqrt, _ => unreachable!(), diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index 1bd32fce8bd9e..c1117e4d811f7 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -159,8 +159,6 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { #[derive(Copy, Clone)] enum FloatBinOp { - /// Arithmetic operation - Arith(mir::BinOp), /// Comparison /// /// The semantics of this operator is a case distinction: we compare the two operands, @@ -247,16 +245,11 @@ impl FloatBinOp { /// Performs `which` scalar operation on `left` and `right` and returns /// the result. fn bin_op_float<'tcx, F: rustc_apfloat::Float>( - this: &crate::MiriInterpCx<'tcx>, which: FloatBinOp, left: &ImmTy<'tcx>, right: &ImmTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { match which { - FloatBinOp::Arith(which) => { - let res = this.binary_op(which, left, right)?; - Ok(res.to_scalar()) - } FloatBinOp::Cmp { gt, lt, eq, unord } => { let left = left.to_scalar().to_float::()?; let right = right.to_scalar().to_float::()?; @@ -323,7 +316,6 @@ fn bin_op_simd_float_first<'tcx, F: rustc_apfloat::Float>( assert_eq!(dest_len, right_len); let res0 = bin_op_float::( - this, which, &this.read_immediate(&this.project_index(&left, 0)?)?, &this.read_immediate(&this.project_index(&right, 0)?)?, @@ -358,7 +350,7 @@ fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>( let right = this.read_immediate(&this.project_index(&right, i)?)?; let dest = this.project_index(&dest, i)?; - let res = bin_op_float::(this, which, &left, &right)?; + let res = bin_op_float::(which, &left, &right)?; this.write_scalar(res, &dest)?; } @@ -367,11 +359,6 @@ fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>( #[derive(Copy, Clone)] enum FloatUnaryOp { - /// sqrt(x) - /// - /// - /// - Sqrt, /// Approximation of 1/x /// /// @@ -392,11 +379,6 @@ fn unary_op_f32<'tcx>( op: &ImmTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { match which { - FloatUnaryOp::Sqrt => { - let op = op.to_scalar(); - // FIXME using host floats - Ok(Scalar::from_u32(f32::from_bits(op.to_u32()?).sqrt().to_bits())) - } FloatUnaryOp::Rcp => { let op = op.to_scalar().to_f32()?; let div = (Single::from_u128(1).value / op).value; diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index 32e8e8a66c13c..07a4eaa85f3e3 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -1,5 +1,4 @@ use rustc_apfloat::ieee::Single; -use rustc_middle::mir; use rustc_span::Symbol; use rustc_target::spec::abi::Abi; @@ -29,18 +28,14 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // performed only on the first element, copying the remaining elements from the input // vector (for binary operations, from the left-hand side). match unprefixed_name { - // Used to implement _mm_{add,sub,mul,div,min,max}_ss functions. + // Used to implement _mm_{min,max}_ss functions. // Performs the operations on the first component of `left` and // `right` and copies the remaining components from `left`. - "add.ss" | "sub.ss" | "mul.ss" | "div.ss" | "min.ss" | "max.ss" => { + "min.ss" | "max.ss" => { let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let which = match unprefixed_name { - "add.ss" => FloatBinOp::Arith(mir::BinOp::Add), - "sub.ss" => FloatBinOp::Arith(mir::BinOp::Sub), - "mul.ss" => FloatBinOp::Arith(mir::BinOp::Mul), - "div.ss" => FloatBinOp::Arith(mir::BinOp::Div), "min.ss" => FloatBinOp::Min, "max.ss" => FloatBinOp::Max, _ => unreachable!(), @@ -65,14 +60,13 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { bin_op_simd_float_all::(this, which, left, right, dest)?; } - // Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions. + // Used to implement _mm_{rcp,rsqrt}_ss functions. // Performs the operations on the first component of `op` and // copies the remaining components from `op`. - "sqrt.ss" | "rcp.ss" | "rsqrt.ss" => { + "rcp.ss" | "rsqrt.ss" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let which = match unprefixed_name { - "sqrt.ss" => FloatUnaryOp::Sqrt, "rcp.ss" => FloatUnaryOp::Rcp, "rsqrt.ss" => FloatUnaryOp::Rsqrt, _ => unreachable!(), @@ -82,11 +76,10 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement _mm_{sqrt,rcp,rsqrt}_ps functions. // Performs the operations on all components of `op`. - "sqrt.ps" | "rcp.ps" | "rsqrt.ps" => { + "rcp.ps" | "rsqrt.ps" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let which = match unprefixed_name { - "sqrt.ps" => FloatUnaryOp::Sqrt, "rcp.ps" => FloatUnaryOp::Rcp, "rsqrt.ps" => FloatUnaryOp::Rsqrt, _ => unreachable!(), diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index 3efdd561d6c60..163d74a6de4e9 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -227,46 +227,6 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { bin_op_simd_float_all::(this, which, left, right, dest)?; } - // Used to implement _mm_sqrt_sd functions. - // Performs the operations on the first component of `op` and - // copies the remaining components from `op`. - "sqrt.sd" => { - let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (op, op_len) = this.operand_to_simd(op)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, op_len); - - let op0 = this.read_scalar(&this.project_index(&op, 0)?)?.to_u64()?; - // FIXME using host floats - let res0 = Scalar::from_u64(f64::from_bits(op0).sqrt().to_bits()); - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; - - for i in 1..dest_len { - this.copy_op(&this.project_index(&op, i)?, &this.project_index(&dest, i)?)?; - } - } - // Used to implement _mm_sqrt_pd functions. - // Performs the operations on all components of `op`. - "sqrt.pd" => { - let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (op, op_len) = this.operand_to_simd(op)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, op_len); - - for i in 0..dest_len { - let op = this.read_scalar(&this.project_index(&op, i)?)?.to_u64()?; - let dest = this.project_index(&dest, i)?; - - // FIXME using host floats - let res = Scalar::from_u64(f64::from_bits(op).sqrt().to_bits()); - - this.write_scalar(res, &dest)?; - } - } // Used to implement the _mm_cmp*_sd functions. // Performs a comparison operation on the first component of `left` // and `right`, returning 0 if false or `u64::MAX` if true. The remaining diff --git a/src/tools/miri/tests/fail/intrinsics/intrinsic_target_feature.rs b/src/tools/miri/tests/fail/intrinsics/intrinsic_target_feature.rs index 9263ad381d1b6..860798f2ab156 100644 --- a/src/tools/miri/tests/fail/intrinsics/intrinsic_target_feature.rs +++ b/src/tools/miri/tests/fail/intrinsics/intrinsic_target_feature.rs @@ -24,7 +24,7 @@ fn main() { unsafe { // Pass, since SSE is enabled - addss(_mm_setzero_ps(), _mm_setzero_ps()); + minss(_mm_setzero_ps(), _mm_setzero_ps()); // Fail, since SSE4.1 is not enabled dpps(_mm_setzero_ps(), _mm_setzero_ps(), 0); @@ -34,8 +34,8 @@ fn main() { #[allow(improper_ctypes)] extern "C" { - #[link_name = "llvm.x86.sse.add.ss"] - fn addss(a: __m128, b: __m128) -> __m128; + #[link_name = "llvm.x86.sse.min.ss"] + fn minss(a: __m128, b: __m128) -> __m128; #[link_name = "llvm.x86.sse41.dpps"] fn dpps(a: __m128, b: __m128, imm8: u8) -> __m128; From 5e25e7c37051df50009b7fad52adacd30eda5824 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:43:52 +0900 Subject: [PATCH 061/685] add a test for ice-3717.rs https://github.com/rust-lang/rust-clippy/issues/13099 --- tests/ui/crashes/ice-3717.fixed | 11 +++++++++++ tests/ui/crashes/ice-3717.rs | 2 -- tests/ui/crashes/ice-3717.stderr | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tests/ui/crashes/ice-3717.fixed diff --git a/tests/ui/crashes/ice-3717.fixed b/tests/ui/crashes/ice-3717.fixed new file mode 100644 index 0000000000000..3f54b326979c9 --- /dev/null +++ b/tests/ui/crashes/ice-3717.fixed @@ -0,0 +1,11 @@ +#![deny(clippy::implicit_hasher)] + +use std::collections::HashSet; + +fn main() {} + +pub fn ice_3717(_: &HashSet) { + //~^ ERROR: parameter of type `HashSet` should be generalized over different hashers + let _ = [0u8; 0]; + let _: HashSet = HashSet::default(); +} diff --git a/tests/ui/crashes/ice-3717.rs b/tests/ui/crashes/ice-3717.rs index 770f6cf448a63..2890a9277c719 100644 --- a/tests/ui/crashes/ice-3717.rs +++ b/tests/ui/crashes/ice-3717.rs @@ -1,7 +1,5 @@ #![deny(clippy::implicit_hasher)] -//@no-rustfix: need to change the suggestion to a multipart suggestion - use std::collections::HashSet; fn main() {} diff --git a/tests/ui/crashes/ice-3717.stderr b/tests/ui/crashes/ice-3717.stderr index 4b4618ee1bbd0..aac72c669654a 100644 --- a/tests/ui/crashes/ice-3717.stderr +++ b/tests/ui/crashes/ice-3717.stderr @@ -1,5 +1,5 @@ error: parameter of type `HashSet` should be generalized over different hashers - --> tests/ui/crashes/ice-3717.rs:9:21 + --> tests/ui/crashes/ice-3717.rs:7:21 | LL | pub fn ice_3717(_: &HashSet) { | ^^^^^^^^^^^^^^ From d4809545eecb8f411415cd45dc0153967a5e5fec Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 7 Aug 2024 15:25:12 +0200 Subject: [PATCH 062/685] allow all code to call getuid() --- src/tools/miri/src/shims/unix/foreign_items.rs | 14 ++++++-------- src/tools/miri/tests/pass-dep/libc/libc-misc.rs | 6 +++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 57930f9807d62..6c35281ecf2fb 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -815,6 +815,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.handle_miri_start_unwind(payload)?; return Ok(EmulateItemResult::NeedsUnwind); } + "getuid" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // For now, just pretend we always have this fixed UID. + this.write_int(UID, dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. @@ -877,13 +882,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - "getuid" - if this.frame_in_std() => { - let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - // For now, just pretend we always have this fixed UID. - this.write_int(super::UID, dest)?; - } - "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish @@ -898,7 +896,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.deref_pointer(result)?; // Must be for "us". - if uid != crate::shims::unix::UID { + if uid != UID { throw_unsup_format!("`getpwuid_r` on other users is not supported"); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs index f7e1d9faa6a27..a5b944e9d426a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs @@ -75,11 +75,15 @@ fn test_dlsym() { assert_eq!(errno, libc::EBADF); } +fn test_getuid() { + let _val = unsafe { libc::getuid() }; +} + fn main() { test_thread_local_errno(); test_environ(); - test_dlsym(); + test_getuid(); #[cfg(target_os = "linux")] test_sigrt(); From 3312f5d65244b4ccb035be7b4c61541afc211914 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 7 Aug 2024 09:06:49 -0700 Subject: [PATCH 063/685] alloc: make `to_string_str!` a bit less complex --- library/alloc/src/string.rs | 57 +++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index d943901e9ecf9..e628be1546f76 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2647,37 +2647,50 @@ impl ToString for i8 { // for strings, including `&&&str`s that would never be written // by hand. This macro generates twelve layers of nested `&`-impl // for primitive strings. -macro_rules! to_string_str { - {type ; x $($x:ident)*} => { - &to_string_str! { type ; $($x)* } - }; - {type ;} => { str }; - {impl ; x $($x:ident)*} => { - to_string_str! { $($x)* } +#[cfg(not(no_global_oom_handling))] +macro_rules! to_string_str_wrap_in_ref { + {x $($x:ident)*} => { + &to_string_str_wrap_in_ref! { $($x)* } }; - {impl ;} => { }; + {} => { str }; +} +#[cfg(not(no_global_oom_handling))] +macro_rules! to_string_expr_wrap_in_deref { {$self:expr ; x $($x:ident)*} => { - *(to_string_str! { $self ; $($x)* }) + *(to_string_expr_wrap_in_deref! { $self ; $($x)* }) }; {$self:expr ;} => { $self }; - {$($x:ident)*} => { - #[doc(hidden)] - #[cfg(not(no_global_oom_handling))] - #[stable(feature = "str_to_string_specialization", since = "1.9.0")] - impl ToString for to_string_str!(type ; $($x)*) { - #[inline] - fn to_string(&self) -> String { - String::from(to_string_str!(self ; $($x)*)) +} +#[cfg(not(no_global_oom_handling))] +macro_rules! to_string_str { + {$($($x:ident)*),+} => { + $( + #[doc(hidden)] + #[stable(feature = "str_to_string_specialization", since = "1.9.0")] + impl ToString for to_string_str_wrap_in_ref!($($x)*) { + #[inline] + fn to_string(&self) -> String { + String::from(to_string_expr_wrap_in_deref!(self ; $($x)*)) + } } - } - to_string_str! { impl ; $($x)* } + )+ }; } +#[cfg(not(no_global_oom_handling))] to_string_str! { - x x x x - x x x x - x x x x + x x x x x x x x x x x x, + x x x x x x x x x x x, + x x x x x x x x x x, + x x x x x x x x x, + x x x x x x x x, + x x x x x x x, + x x x x x x, + x x x x x, + x x x x, + x x x, + x x, + x, } #[doc(hidden)] From 9ebe5ae3064af4f7f0f79c774e778ee26f36bdcb Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Wed, 24 Jul 2024 22:56:38 +0000 Subject: [PATCH 064/685] initial implementation of mergable rustdoc cci --- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/clean/types.rs | 2 +- src/librustdoc/config.rs | 1 - src/librustdoc/html/render/context.rs | 10 +- src/librustdoc/html/render/mod.rs | 2 + src/librustdoc/html/render/search_index.rs | 34 +- src/librustdoc/html/render/sorted_json.rs | 82 + src/librustdoc/html/render/sorted_template.rs | 136 ++ src/librustdoc/html/render/tests.rs | 271 +++ src/librustdoc/html/render/write_shared.rs | 1462 +++++++++-------- 10 files changed, 1317 insertions(+), 685 deletions(-) create mode 100644 src/librustdoc/html/render/sorted_json.rs create mode 100644 src/librustdoc/html/render/sorted_template.rs diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index b3fccbf6456e0..67ba8c773175c 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -16,7 +16,7 @@ minifier = "0.3.0" pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -serde_json = "1.0" +serde_json = { version = "1.0", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 4850500a1bfae..542e810b5cfa8 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -128,7 +128,7 @@ pub(crate) struct ExternalCrate { } impl ExternalCrate { - const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; + pub(crate) const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; #[inline] pub(crate) fn def_id(&self) -> DefId { diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e4549796b3e83..2e54a22840bb5 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -730,7 +730,6 @@ impl Options { let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); - if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { dcx.fatal( "--generate-link-to-definition option can only be used with HTML output format", diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 0334eacc16149..8e72dd6a864ae 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -14,7 +14,6 @@ use rustc_span::edition::Edition; use rustc_span::{sym, FileName, Symbol}; use super::print_item::{full_path, item_path, print_item}; -use super::search_index::build_index; use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar}; use super::write_shared::write_shared; use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; @@ -573,13 +572,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { } if !no_emit_shared { - // Build our search index - let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); - - // Write shared runs within a flock; disable thread dispatching of IO temporarily. - Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); - write_shared(&mut cx, &krate, index, &md_opts)?; - Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + write_shared(&mut cx, &krate, &md_opts, tcx)?; } Ok((cx, krate)) @@ -729,6 +722,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ); shared.fs.write(help_file, v)?; + // if to avoid writing files to doc root unless we're on the final invocation if shared.layout.scrape_examples_extension { page.title = "About scraped examples"; page.description = "How the scraped examples feature works in Rustdoc"; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9074e40a53614..4b1c9b4af474a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -31,6 +31,8 @@ mod tests; mod context; mod print_item; pub(crate) mod sidebar; +mod sorted_json; +mod sorted_template; mod span_map; mod type_layout; mod write_shared; diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 8a2f31f7413e1..184e5afba3c99 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -18,6 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; +use crate::html::render::sorted_json::SortedJson; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// The serialized search description sharded version @@ -46,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features pub(crate) struct SerializedSearchIndex { - pub(crate) index: String, + pub(crate) index: SortedJson, pub(crate) desc: Vec<(usize, String)>, } @@ -683,24 +684,19 @@ pub(crate) fn build_index<'tcx>( // The index, which is actually used to search, is JSON // It uses `JSON.parse(..)` to actually load, since JSON // parses faster than the full JavaScript syntax. - let index = format!( - r#"["{}",{}]"#, - krate.name(tcx), - serde_json::to_string(&CrateData { - items: crate_items, - paths: crate_paths, - aliases: &aliases, - associated_item_disambiguators: &associated_item_disambiguators, - desc_index, - empty_desc, - }) - .expect("failed serde conversion") - // All these `replace` calls are because we have to go through JS string for JSON content. - .replace('\\', r"\\") - .replace('\'', r"\'") - // We need to escape double quotes for the JSON. - .replace("\\\"", "\\\\\"") - ); + let crate_name = krate.name(tcx); + let data = CrateData { + items: crate_items, + paths: crate_paths, + aliases: &aliases, + associated_item_disambiguators: &associated_item_disambiguators, + desc_index, + empty_desc, + }; + let index = SortedJson::array_unsorted([ + SortedJson::serialize(crate_name.as_str()), + SortedJson::serialize(data), + ]); SerializedSearchIndex { index, desc } } diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/sorted_json.rs new file mode 100644 index 0000000000000..3a097733b8b20 --- /dev/null +++ b/src/librustdoc/html/render/sorted_json.rs @@ -0,0 +1,82 @@ +use itertools::Itertools as _; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::borrow::Borrow; +use std::fmt; + +/// Prerenedered json. +/// +/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified +/// keys. +/// +/// Must use serde_json with the preserve_order feature. +/// +/// Both the Display and serde_json::to_string implementations write the serialized json +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(from = "Value")] +#[serde(into = "Value")] +pub(crate) struct SortedJson(String); + +impl SortedJson { + /// If you pass in an array, it will not be sorted. + pub(crate) fn serialize(item: T) -> Self { + SortedJson(serde_json::to_string(&item).unwrap()) + } + + /// Serializes and sorts + pub(crate) fn array, I: IntoIterator>(items: I) -> Self { + let items = items + .into_iter() + .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) + .format_with(",", |item, f| f(item.borrow())); + SortedJson(format!("[{}]", items)) + } + + pub(crate) fn array_unsorted, I: IntoIterator>( + items: I, + ) -> Self { + let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); + SortedJson(format!("[{items}]")) + } +} + +impl fmt::Display for SortedJson { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for SortedJson { + fn from(value: Value) -> Self { + SortedJson(serde_json::to_string(&value).unwrap()) + } +} + +impl From for Value { + fn from(json: SortedJson) -> Self { + serde_json::from_str(&json.0).unwrap() + } +} + +/// For use in JSON.parse('{...}'). +/// +/// JSON.parse supposedly loads faster than raw JS source, +/// so this is used for large objects. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct EscapedJson(SortedJson); + +impl From for EscapedJson { + fn from(json: SortedJson) -> Self { + EscapedJson(json) + } +} + +impl fmt::Display for EscapedJson { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // All these `replace` calls are because we have to go through JS string + // for JSON content. + // We need to escape double quotes for the JSON + let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); + write!(f, "{}", json) + } +} diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs new file mode 100644 index 0000000000000..95240616b01dc --- /dev/null +++ b/src/librustdoc/html/render/sorted_template.rs @@ -0,0 +1,136 @@ +use std::collections::BTreeSet; +use std::fmt; +use std::marker::PhantomData; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +/// Append-only templates for sorted, deduplicated lists of items. +/// +/// Last line of the rendered output is a comment encoding the next insertion point. +#[derive(Debug, Clone)] +pub(crate) struct SortedTemplate { + format: PhantomData, + before: String, + after: String, + contents: BTreeSet, +} + +/// Written to last line of file to specify the location of each fragment +#[derive(Serialize, Deserialize, Debug, Clone)] +struct Offset { + /// Index of the first byte in the template + start: usize, + /// The length of each fragment in the encoded template, including the separator + delta: Vec, +} + +impl SortedTemplate { + /// Generate this template from arbitary text. + /// Will insert wherever the substring `magic` can be found. + /// Errors if it does not appear exactly once. + pub(crate) fn magic(template: &str, magic: &str) -> Result { + let mut split = template.split(magic); + let before = split.next().ok_or(Error)?; + let after = split.next().ok_or(Error)?; + if split.next().is_some() { + return Err(Error); + } + Ok(Self::before_after(before, after)) + } + + /// Template will insert contents between `before` and `after` + pub(crate) fn before_after(before: S, after: T) -> Self { + let before = before.to_string(); + let after = after.to_string(); + SortedTemplate { format: PhantomData, before, after, contents: Default::default() } + } +} + +impl SortedTemplate { + /// Adds this text to the template + pub(crate) fn append(&mut self, insert: String) { + self.contents.insert(insert); + } +} + +impl fmt::Display for SortedTemplate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut delta = Vec::default(); + write!(f, "{}", self.before)?; + let contents: Vec<_> = self.contents.iter().collect(); + let mut sep = ""; + for content in contents { + delta.push(sep.len() + content.len()); + write!(f, "{}{}", sep, content)?; + sep = F::SEPARATOR; + } + let offset = Offset { start: self.before.len(), delta }; + let offset = serde_json::to_string(&offset).unwrap(); + write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?; + Ok(()) + } +} + +fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> { + s.is_char_boundary(index).then(|| s.split_at(index)) +} + +impl FromStr for SortedTemplate { + type Err = Error; + fn from_str(s: &str) -> Result { + let (s, offset) = s.rsplit_once("\n").ok_or(Error)?; + let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?; + let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?; + let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?; + let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?; + let mut contents = BTreeSet::default(); + let mut sep = ""; + for &index in offset.delta.iter() { + let (content, rest) = checked_split_at(s, index).ok_or(Error)?; + s = rest; + let content = content.strip_prefix(sep).ok_or(Error)?; + contents.insert(content.to_string()); + sep = F::SEPARATOR; + } + Ok(SortedTemplate { + format: PhantomData, + before: before.to_string(), + after: s.to_string(), + contents, + }) + } +} + +pub(crate) trait FileFormat { + const COMMENT_START: &'static str; + const COMMENT_END: &'static str; + const SEPARATOR: &'static str; +} + +#[derive(Debug, Clone)] +pub(crate) struct Html; + +impl FileFormat for Html { + const COMMENT_START: &'static str = ""; + const SEPARATOR: &'static str = ""; +} + +#[derive(Debug, Clone)] +pub(crate) struct Js; + +impl FileFormat for Js { + const COMMENT_START: &'static str = "//"; + const COMMENT_END: &'static str = ""; + const SEPARATOR: &'static str = ","; +} + +#[derive(Debug, Clone)] +pub(crate) struct Error; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid template") + } +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 4a9724a6f840f..16e67b0f1180e 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -52,3 +52,274 @@ fn test_all_types_prints_header_once() { assert_eq!(1, buffer.into_inner().matches("List of all items").count()); } + +mod sorted_json { + use super::super::sorted_json::*; + + fn check(json: SortedJson, serialized: &str) { + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = json.to_string(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = serde_json::to_string(&json).unwrap(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + } + + #[test] + fn escape_json_number() { + let json = SortedJson::serialize(3); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), "3"); + } + + #[test] + fn escape_json_single_quote() { + let json = SortedJson::serialize("he's"); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\'s""#); + } + + #[test] + fn escape_json_array() { + let json = SortedJson::serialize([1, 2, 3]); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#"[1,2,3]"#); + } + + #[test] + fn escape_json_string() { + let json = SortedJson::serialize(r#"he"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\"llo""#); + } + + #[test] + fn escape_json_string_escaped() { + let json = SortedJson::serialize(r#"he\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); + } + + #[test] + fn escape_json_string_escaped_escaped() { + let json = SortedJson::serialize(r#"he\\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); + } + + #[test] + fn number() { + let json = SortedJson::serialize(3); + let serialized = "3"; + check(json, serialized); + } + + #[test] + fn boolean() { + let json = SortedJson::serialize(true); + let serialized = "true"; + check(json, serialized); + } + + #[test] + fn string() { + let json = SortedJson::serialize("he\"llo"); + let serialized = r#""he\"llo""#; + check(json, serialized); + } + + #[test] + fn serialize_array() { + let json = SortedJson::serialize([3, 1, 2]); + let serialized = "[3,1,2]"; + check(json, serialized); + } + + #[test] + fn sorted_array() { + let items = ["c", "a", "b"]; + let serialized = r#"["a","b","c"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array(items); + check(json, serialized); + } + + #[test] + fn nested_array() { + let a = SortedJson::serialize(3); + let b = SortedJson::serialize(2); + let c = SortedJson::serialize(1); + let d = SortedJson::serialize([1, 3, 2]); + let json = SortedJson::array([a, b, c, d]); + let serialized = r#"[1,2,3,[1,3,2]]"#; + check(json, serialized); + } + + #[test] + fn array_unsorted() { + let items = ["c", "a", "b"]; + let serialized = r#"["c","a","b"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array_unsorted(items); + check(json, serialized); + } +} + +mod sorted_template { + use super::super::sorted_template::*; + use std::str::FromStr; + + fn is_comment_js(s: &str) -> bool { + s.starts_with("//") + } + + fn is_comment_html(s: &str) -> bool { + // not correct but good enough for these tests + s.starts_with("") + } + + #[test] + fn html_from_empty() { + let inserts = ["

hello

", "

kind

", "

hello

", "

world

"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "

hello

kind

world

"); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn html_page() { + let inserts = ["

hello

", "

kind

", "

world

"]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_from_empty() { + let inserts = ["1", "2", "2", "2", "3", "1"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3"); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_empty_array() { + let template = SortedTemplate::::before_after("[", "]"); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_number_array() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1,2,3]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn magic_js_number_array() { + let inserts = ["1", "1"]; + let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn round_trip_js() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "[1,2,3,4]"); + assert!(is_comment_js(end)); + } + + #[test] + fn round_trip_html() { + let inserts = ["

hello

", "

kind

", "

world

", "

kind

"]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + template.append(inserts[0].to_string()); + template.append(inserts[1].to_string()); + let template = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + template.append(inserts[2].to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}

hello

kind

world

{after}")); + assert!(is_comment_html(end)); + } + + #[test] + fn blank_js() { + let inserts = ["1", "2", "3"]; + let template = SortedTemplate::::before_after("", ""); + let template = format!("{template}"); + let (t, _) = template.rsplit_once("\n").unwrap(); + assert_eq!(t, ""); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3,4"); + assert!(is_comment_js(end)); + } +} diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8fd56eae37ffc..eaebeadd8817e 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,19 +1,43 @@ +//! Rustdoc writes out two kinds of shared files: +//! - Static files, which are embedded in the rustdoc binary and are written with a +//! filename that includes a hash of their contents. These will always have a new +//! URL if the contents change, so they are safe to cache with the +//! `Cache-Control: immutable` directive. They are written under the static.files/ +//! directory and are written when --emit-type is empty (default) or contains +//! "toolchain-specific". If using the --static-root-path flag, it should point +//! to a URL path prefix where each of these filenames can be fetched. +//! - Invocation specific files. These are generated based on the crate(s) being +//! documented. Their filenames need to be predictable without knowing their +//! contents, so they do not include a hash in their filename and are not safe to +//! cache with `Cache-Control: immutable`. They include the contents of the +//! --resource-suffix flag and are emitted when --emit-type is empty (default) +//! or contains "invocation-specific". + +use std::any::Any; use std::cell::RefCell; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::{self, BufReader}; -use std::path::{Component, Path}; +use std::collections::hash_map::Entry; +use std::ffi::OsString; +use std::fs::File; +use std::io::BufWriter; +use std::io::Write as _; +use std::iter::once; +use std::marker::PhantomData; +use std::path::{Component, Path, PathBuf}; use std::rc::{Rc, Weak}; +use std::str::FromStr; +use std::{fmt, fs, io}; use indexmap::IndexMap; use itertools::Itertools; +use regex::Regex; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Symbol; use serde::ser::SerializeSeq; -use serde::{Serialize, Serializer}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; @@ -24,53 +48,92 @@ use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; +use crate::html::layout; +use crate::html::render::search_index::build_index; use crate::html::render::search_index::SerializedSearchIndex; +use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::sorted_template::{self, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; -use crate::html::{layout, static_files}; +use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; use crate::{try_err, try_none}; -/// Rustdoc writes out two kinds of shared files: -/// - Static files, which are embedded in the rustdoc binary and are written with a -/// filename that includes a hash of their contents. These will always have a new -/// URL if the contents change, so they are safe to cache with the -/// `Cache-Control: immutable` directive. They are written under the static.files/ -/// directory and are written when --emit-type is empty (default) or contains -/// "toolchain-specific". If using the --static-root-path flag, it should point -/// to a URL path prefix where each of these filenames can be fetched. -/// - Invocation specific files. These are generated based on the crate(s) being -/// documented. Their filenames need to be predictable without knowing their -/// contents, so they do not include a hash in their filename and are not safe to -/// cache with `Cache-Control: immutable`. They include the contents of the -/// --resource-suffix flag and are emitted when --emit-type is empty (default) -/// or contains "invocation-specific". -pub(super) fn write_shared( +/// Write crate-info.json cross-crate information, static files, invocation-specific files, etc. to disk +pub(crate) fn write_shared( cx: &mut Context<'_>, krate: &Crate, - search_index: SerializedSearchIndex, - options: &RenderOptions, + opt: &RenderOptions, + tcx: TyCtxt<'_>, ) -> Result<(), Error> { - // Write out the shared files. Note that these are shared among all rustdoc - // docs placed in the output directory, so this needs to be a synchronized - // operation with respect to all other rustdocs running around. + // NOTE(EtomicBomb): I don't think we need sync here because no read-after-write? + Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); let lock_file = cx.dst.join(".lock"); + // Write shared runs within a flock; disable thread dispatching of IO temporarily. let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); - // InvocationSpecific resources should always be dynamic. - let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result, Error>| { - let content = make_content()?; - if options.emit.is_empty() || options.emit.contains(&EmitType::InvocationSpecific) { - let output_filename = static_files::suffix_path(p, &cx.shared.resource_suffix); - cx.shared.fs.write(cx.dst.join(output_filename), content) - } else { - Ok(()) - } + let SerializedSearchIndex { index, desc } = + build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); + write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally + + let crate_name = krate.name(cx.tcx()); + let crate_name = crate_name.as_str(); // rand + let crate_name_json = SortedJson::serialize(crate_name); // "rand" + let external_crates = hack_get_external_crate_names(cx)?; + let info = CrateInfo { + src_files_js: SourcesPart::get(cx, &crate_name_json)?, + search_index_js: SearchIndexPart::get(cx, index)?, + all_crates: AllCratesPart::get(crate_name_json.clone())?, + crates_index: CratesIndexPart::get(&crate_name, &external_crates)?, + trait_impl: TraitAliasPart::get(cx, &crate_name_json)?, + type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?, }; - cx.shared - .fs - .create_dir_all(cx.dst.join("static.files")) - .map_err(|e| PathError::new(e, "static.files"))?; + let crates_info = vec![info]; // we have info from just one crate + + write_static_files(cx, &opt)?; + let dst = &cx.dst; + if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { + if cx.include_sources { + write_rendered_cci::(SourcesPart::blank, dst, &crates_info)?; + } + write_rendered_cci::( + SearchIndexPart::blank, + dst, + &crates_info, + )?; + write_rendered_cci::(AllCratesPart::blank, dst, &crates_info)?; + } + write_rendered_cci::(TraitAliasPart::blank, dst, &crates_info)?; + write_rendered_cci::(TypeAliasPart::blank, dst, &crates_info)?; + match &opt.index_page { + Some(index_page) if opt.enable_index_page => { + let mut md_opts = opt.clone(); + md_opts.output = cx.dst.clone(); + md_opts.external_html = cx.shared.layout.external_html.clone(); + try_err!( + crate::markdown::render(&index_page, md_opts, cx.shared.edition()), + &index_page + ); + } + None if opt.enable_index_page => { + write_rendered_cci::( + || CratesIndexPart::blank(cx), + dst, + &crates_info, + )?; + } + _ => {} // they don't want an index page + } + + Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + Ok(()) +} + +/// Writes the static files, the style files, and the css extensions +fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> { + let static_dir = cx.dst.join("static.files"); + + cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?; // Handle added third-party themes for entry in &cx.shared.style_files { @@ -97,680 +160,769 @@ pub(super) fn write_shared( } if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) { - let static_dir = cx.dst.join(Path::new("static.files")); static_files::for_each(|f: &static_files::StaticFile| { let filename = static_dir.join(f.output_filename()); cx.shared.fs.write(filename, f.minified()) })?; } - /// Read a file and return all lines that match the `"{crate}":{data},` format, - /// and return a tuple `(Vec, Vec)`. - /// - /// This forms the payload of files that look like this: - /// - /// ```javascript - /// var data = { - /// "{crate1}":{data}, - /// "{crate2}":{data} - /// }; - /// use_data(data); - /// ``` - /// - /// The file needs to be formatted so that *only crate data lines start with `"`*. - fn collect(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); - - if path.exists() { - let prefix = format!("\"{krate}\""); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with('"') { - continue; - } - if line.starts_with(&prefix) { - continue; - } - if line.ends_with(',') { - ret.push(line[..line.len() - 1].to_string()); - } else { - // No comma (it's the case for the last added crate line) - ret.push(line.to_string()); - } - krates.push( - line.split('"') - .find(|s| !s.is_empty()) - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) - } - - /// Read a file and return all lines that match the "{crate}":{data},\ format, - /// and return a tuple `(Vec, Vec)`. - /// - /// This forms the payload of files that look like this: - /// - /// ```javascript - /// var data = JSON.parse('{\ - /// "{crate1}":{data},\ - /// "{crate2}":{data}\ - /// }'); - /// use_data(data); - /// ``` - /// - /// The file needs to be formatted so that *only crate data lines start with `"`*. - fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); - - if path.exists() { - let prefix = format!("[\"{krate}\""); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with("[\"") { - continue; - } - if line.starts_with(&prefix) { - continue; - } - if line.ends_with("],\\") { - ret.push(line[..line.len() - 2].to_string()); - } else { - // Ends with "\\" (it's the case for the last added crate line) - ret.push(line[..line.len() - 1].to_string()); - } - krates.push( - line[1..] // We skip the `[` parent at the beginning of the line. - .split('"') - .find(|s| !s.is_empty()) - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) + Ok(()) +} + +/// Write the search description shards to disk +fn write_search_desc( + cx: &mut Context<'_>, + krate: &Crate, + search_desc: &[(usize, String)], +) -> Result<(), Error> { + let crate_name = krate.name(cx.tcx()).to_string(); + let encoded_crate_name = SortedJson::serialize(&crate_name); + let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]); + if Path::new(&path).exists() { + try_err!(fs::remove_dir_all(&path), &path); + } + for (i, (_, part)) in search_desc.iter().enumerate() { + let filename = static_files::suffix_path( + &format!("{crate_name}-desc-{i}-.js"), + &cx.shared.resource_suffix, + ); + let path = path.join(filename); + let part = SortedJson::serialize(&part); + let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); + write_create_parents(&path, part)?; } + Ok(()) +} - use std::ffi::OsString; +/// Written to `crate-info.json`. Contains pre-rendered contents to insert into the CCI template +#[derive(Serialize, Deserialize, Clone, Debug)] +struct CrateInfo { + src_files_js: PartsAndLocations, + search_index_js: PartsAndLocations, + all_crates: PartsAndLocations, + crates_index: PartsAndLocations, + trait_impl: PartsAndLocations, + type_impl: PartsAndLocations, +} - #[derive(Debug, Default)] - struct Hierarchy { - parent: Weak, - elem: OsString, - children: RefCell>>, - elems: RefCell>, +impl CrateInfo { + /// Gets a reference to the cross-crate information parts for `T` + fn get(&self) -> &PartsAndLocations { + (&self.src_files_js as &dyn Any) + .downcast_ref() + .or_else(|| (&self.search_index_js as &dyn Any).downcast_ref()) + .or_else(|| (&self.all_crates as &dyn Any).downcast_ref()) + .or_else(|| (&self.crates_index as &dyn Any).downcast_ref()) + .or_else(|| (&self.trait_impl as &dyn Any).downcast_ref()) + .or_else(|| (&self.type_impl as &dyn Any).downcast_ref()) + .expect("this should be an exhaustive list of `CciPart`s") } +} - impl Hierarchy { - fn with_parent(elem: OsString, parent: &Rc) -> Self { - Self { elem, parent: Rc::downgrade(parent), ..Self::default() } - } +/// Paths (relative to the doc root) and their pre-merge contents +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +struct PartsAndLocations

{ + parts: Vec<(PathBuf, P)>, +} - fn to_json_string(&self) -> String { - let borrow = self.children.borrow(); - let mut subs: Vec<_> = borrow.values().collect(); - subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem)); - let mut files = self - .elems - .borrow() - .iter() - .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion"))) - .collect::>(); - files.sort_unstable(); - let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(","); - let dirs = if subs.is_empty() && files.is_empty() { - String::new() - } else { - format!(",[{subs}]") - }; - let files = files.join(","); - let files = if files.is_empty() { String::new() } else { format!(",[{files}]") }; - format!( - "[\"{name}\"{dirs}{files}]", - name = self.elem.to_str().expect("invalid osstring conversion"), - dirs = dirs, - files = files - ) - } +impl

Default for PartsAndLocations

{ + fn default() -> Self { + Self { parts: Vec::default() } + } +} - fn add_path(self: &Rc, path: &Path) { - let mut h = Rc::clone(&self); - let mut elems = path - .components() - .filter_map(|s| match s { - Component::Normal(s) => Some(s.to_owned()), - Component::ParentDir => Some(OsString::from("..")), - _ => None, - }) - .peekable(); - loop { - let cur_elem = elems.next().expect("empty file path"); - if cur_elem == ".." { - if let Some(parent) = h.parent.upgrade() { - h = parent; - } - continue; - } - if elems.peek().is_none() { - h.elems.borrow_mut().insert(cur_elem); - break; - } else { - let entry = Rc::clone( - h.children - .borrow_mut() - .entry(cur_elem.clone()) - .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))), - ); - h = entry; - } - } +impl PartsAndLocations> { + fn push(&mut self, path: PathBuf, item: U) { + self.parts.push((path, Part { _artifact: PhantomData, item })); + } + + /// Singleton part, one file + fn with(path: PathBuf, part: U) -> Self { + let mut ret = Self::default(); + ret.push(path, part); + ret + } +} + +/// A piece of one of the shared artifacts for documentation (search index, sources, alias list, etc.) +/// +/// Merged at a user specified time and written to the `doc/` directory +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +struct Part { + #[serde(skip)] + _artifact: PhantomData, + item: U, +} + +impl fmt::Display for Part { + /// Writes serialized JSON + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.item) + } +} + +/// Wrapper trait for `Part` +trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static { + /// Identifies the file format of the cross-crate information + type FileFormat: sorted_template::FileFormat; +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct SearchIndex; +type SearchIndexPart = Part; +impl CciPart for SearchIndexPart { + type FileFormat = sorted_template::Js; +} + +impl SearchIndexPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"var searchIndex = new Map(JSON.parse('[", + r"]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);", + ) + } + + fn get(cx: &Context<'_>, search_index: SortedJson) -> Result, Error> { + let path = suffix_path("search-index.js", &cx.shared.resource_suffix); + let search_index = EscapedJson::from(search_index); + Ok(PartsAndLocations::with(path, search_index)) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct AllCrates; +type AllCratesPart = Part; +impl CciPart for AllCratesPart { + type FileFormat = sorted_template::Js; +} + +impl AllCratesPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after("window.ALL_CRATES = [", "];") + } + + fn get(crate_name_json: SortedJson) -> Result, Error> { + // external hack_get_external_crate_names not needed here, because + // there's no way that we write the search index but not crates.js + let path = PathBuf::from("crates.js"); + Ok(PartsAndLocations::with(path, crate_name_json)) + } +} + +/// Reads `crates.js`, which seems like the best +/// place to obtain the list of externally documented crates if the index +/// page was disabled when documenting the deps. +/// +/// This is to match the current behavior of rustdoc, which allows you to get all crates +/// on the index page, even if --enable-index-page is only passed to the last crate. +fn hack_get_external_crate_names(cx: &Context<'_>) -> Result, Error> { + let path = cx.dst.join("crates.js"); + let Ok(content) = fs::read_to_string(&path) else { + // they didn't emit invocation specific, so we just say there were no crates + return Ok(Vec::default()); + }; + // this is only run once so it's fine not to cache it + // !dot_matches_new_line: all crates on same line. greedy: match last bracket + let regex = Regex::new(r"\[.*\]").unwrap(); + let Some(content) = regex.find(&content) else { + return Err(Error::new("could not find crates list in crates.js", path)); + }; + let content: Vec = try_err!(serde_json::from_str(content.as_str()), &path); + Ok(content) +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct CratesIndex; +type CratesIndexPart = Part; +impl CciPart for CratesIndexPart { + type FileFormat = sorted_template::Html; +} + +impl CratesIndexPart { + fn blank(cx: &Context<'_>) -> SortedTemplate<::FileFormat> { + let page = layout::Page { + title: "Index of crates", + css_class: "mod sys", + root_path: "./", + static_root_path: cx.shared.static_root_path.as_deref(), + description: "List of crates", + resource_suffix: &cx.shared.resource_suffix, + rust_logo: true, + }; + let layout = &cx.shared.layout; + let style_files = &cx.shared.style_files; + const MAGIC: &str = "\u{FFFC}"; // users are being naughty if they have this + let content = format!("

List of all crates

    {MAGIC}
"); + let template = layout::render(layout, &page, "", content, &style_files); + match SortedTemplate::magic(&template, MAGIC) { + Ok(template) => template, + Err(e) => panic!( + "{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page" + ), } } - if cx.include_sources { - let hierarchy = Rc::new(Hierarchy::default()); - for source in cx - .shared - .local_sources - .iter() - .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) - { - hierarchy.add_path(source); + /// Might return parts that are duplicate with ones in prexisting index.html + fn get(crate_name: &str, external_crates: &[String]) -> Result, Error> { + let mut ret = PartsAndLocations::default(); + let path = PathBuf::from("index.html"); + for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) { + let part = format!( + "
  • {crate_name}
  • ", + trailing_slash = ensure_trailing_slash(crate_name), + ); + ret.push(path.clone(), part); } - let hierarchy = Rc::try_unwrap(hierarchy).unwrap(); - let dst = cx.dst.join(&format!("src-files{}.js", cx.shared.resource_suffix)); - let make_sources = || { - let (mut all_sources, _krates) = - try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); - all_sources.push(format!( - r#"["{}",{}]"#, - &krate.name(cx.tcx()), - hierarchy - .to_json_string() - // All these `replace` calls are because we have to go through JS string for JSON content. - .replace('\\', r"\\") - .replace('\'', r"\'") - // We need to escape double quotes for the JSON. - .replace("\\\"", "\\\\\"") - )); - all_sources.sort(); - // This needs to be `var`, not `const`. - // This variable needs declared in the current global scope so that if - // src-script.js loads first, it can pick it up. - let mut v = String::from("var srcIndex = new Map(JSON.parse('[\\\n"); - v.push_str(&all_sources.join(",\\\n")); - v.push_str("\\\n]'));\ncreateSrcSidebar();\n"); - Ok(v.into_bytes()) - }; - write_invocation_specific("src-files.js", &make_sources)?; + Ok(ret) } +} - // Update the search index and crate list. - let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); - let (mut all_indexes, mut krates) = - try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); - all_indexes.push(search_index.index); - krates.push(krate.name(cx.tcx()).to_string()); - krates.sort(); +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct Sources; +type SourcesPart = Part; +impl CciPart for SourcesPart { + type FileFormat = sorted_template::Js; +} - // Sort the indexes by crate so the file will be generated identically even - // with rustdoc running in parallel. - all_indexes.sort(); - write_invocation_specific("search-index.js", &|| { +impl SourcesPart { + fn blank() -> SortedTemplate<::FileFormat> { // This needs to be `var`, not `const`. // This variable needs declared in the current global scope so that if - // search.js loads first, it can pick it up. - let mut v = String::from("var searchIndex = new Map(JSON.parse('[\\\n"); - v.push_str(&all_indexes.join(",\\\n")); - v.push_str( - r#"\ -]')); -if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; -else if (window.initSearch) window.initSearch(searchIndex); -"#, - ); - Ok(v.into_bytes()) - })?; - - let search_desc_dir = cx.dst.join(format!("search.desc/{krate}", krate = krate.name(cx.tcx()))); - if Path::new(&search_desc_dir).exists() { - try_err!(std::fs::remove_dir_all(&search_desc_dir), &search_desc_dir); - } - try_err!(std::fs::create_dir_all(&search_desc_dir), &search_desc_dir); - let kratename = krate.name(cx.tcx()).to_string(); - for (i, (_, data)) in search_index.desc.into_iter().enumerate() { - let output_filename = static_files::suffix_path( - &format!("{kratename}-desc-{i}-.js"), - &cx.shared.resource_suffix, - ); - let path = search_desc_dir.join(output_filename); - try_err!( - std::fs::write( - &path, - &format!( - r##"searchState.loadedDescShard({kratename}, {i}, {data})"##, - kratename = serde_json::to_string(&kratename).unwrap(), - data = serde_json::to_string(&data).unwrap(), - ) - .into_bytes() - ), - &path - ); + // src-script.js loads first, it can pick it up. + SortedTemplate::before_after( + r"var srcIndex = new Map(JSON.parse('[", + r"]')); +createSrcSidebar();", + ) } - write_invocation_specific("crates.js", &|| { - let krates = krates.iter().map(|k| format!("\"{k}\"")).join(","); - Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes()) - })?; + fn get(cx: &Context<'_>, crate_name: &SortedJson) -> Result, Error> { + let hierarchy = Rc::new(Hierarchy::default()); + cx.shared + .local_sources + .iter() + .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) + .for_each(|source| hierarchy.add_path(source)); + let path = suffix_path("src-files.js", &cx.shared.resource_suffix); + let hierarchy = hierarchy.to_json_string(); + let part = SortedJson::array_unsorted([crate_name, &hierarchy]); + let part = EscapedJson::from(part); + Ok(PartsAndLocations::with(path, part)) + } +} - if options.enable_index_page { - if let Some(index_page) = options.index_page.clone() { - let mut md_opts = options.clone(); - md_opts.output = cx.dst.clone(); - md_opts.external_html = (*cx.shared).layout.external_html.clone(); +/// Source files directory tree +#[derive(Debug, Default)] +struct Hierarchy { + parent: Weak, + elem: OsString, + children: RefCell>>, + elems: RefCell>, +} - crate::markdown::render(&index_page, md_opts, cx.shared.edition()) - .map_err(|e| Error::new(e, &index_page))?; - } else { - let shared = Rc::clone(&cx.shared); - let dst = cx.dst.join("index.html"); - let page = layout::Page { - title: "Index of crates", - css_class: "mod sys", - root_path: "./", - static_root_path: shared.static_root_path.as_deref(), - description: "List of crates", - resource_suffix: &shared.resource_suffix, - rust_logo: true, - }; +impl Hierarchy { + fn with_parent(elem: OsString, parent: &Rc) -> Self { + Self { elem, parent: Rc::downgrade(parent), ..Self::default() } + } - let content = format!( - "

    List of all crates

      {}
    ", - krates.iter().format_with("", |k, f| { - f(&format_args!( - "
  • {k}
  • ", - trailing_slash = ensure_trailing_slash(k), - )) - }) - ); - let v = layout::render(&shared.layout, &page, "", content, &shared.style_files); - shared.fs.write(dst, v)?; + fn to_json_string(&self) -> SortedJson { + let subs = self.children.borrow(); + let files = self.elems.borrow(); + let name = SortedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")); + let mut out = Vec::from([name]); + if !subs.is_empty() || !files.is_empty() { + let subs = subs.iter().map(|(_, s)| s.to_json_string()); + out.push(SortedJson::array(subs)); } + if !files.is_empty() { + let files = + files.iter().map(|s| SortedJson::serialize(s.to_str().expect("invalid osstring"))); + out.push(SortedJson::array(files)); + } + SortedJson::array_unsorted(out) } - let cloned_shared = Rc::clone(&cx.shared); - let cache = &cloned_shared.cache; - - // Collect the list of aliased types and their aliases. - // - // - // The clean AST has type aliases that point at their types, but - // this visitor works to reverse that: `aliased_types` is a map - // from target to the aliases that reference it, and each one - // will generate one file. - struct TypeImplCollector<'cx, 'cache> { - // Map from DefId-of-aliased-type to its data. - aliased_types: IndexMap>, - visited_aliases: FxHashSet, - cache: &'cache Cache, - cx: &'cache mut Context<'cx>, - } - // Data for an aliased type. - // - // In the final file, the format will be roughly: - // - // ```json - // // type.impl/CRATE/TYPENAME.js - // JSONP( - // "CRATE": [ - // ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...], - // ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...], - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType - // ... - // ] - // ) - // ``` - struct AliasedType<'cache> { - // This is used to generate the actual filename of this aliased type. - target_fqp: &'cache [Symbol], - target_type: ItemType, - // This is the data stored inside the file. - // ItemId is used to deduplicate impls. - impl_: IndexMap>, - } - // The `impl_` contains data that's used to figure out if an alias will work, - // and to generate the HTML at the end. - // - // The `type_aliases` list is built up with each type alias that matches. - struct AliasedTypeImpl<'cache> { - impl_: &'cache Impl, - type_aliases: Vec<(&'cache [Symbol], Item)>, - } - impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> { - fn visit_item(&mut self, it: &Item) { - self.visit_item_recur(it); - let cache = self.cache; - let ItemKind::TypeAliasItem(ref t) = *it.kind else { return }; - let Some(self_did) = it.item_id.as_def_id() else { return }; - if !self.visited_aliases.insert(self_did) { - return; - } - let Some(target_did) = t.type_.def_id(cache) else { return }; - let get_extern = { || cache.external_paths.get(&target_did) }; - let Some(&(ref target_fqp, target_type)) = - cache.paths.get(&target_did).or_else(get_extern) - else { - return; - }; - let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| { - let impl_ = cache - .impls - .get(&target_did) - .map(|v| &v[..]) - .unwrap_or_default() - .iter() - .map(|impl_| { - ( - impl_.impl_item.item_id, - AliasedTypeImpl { impl_, type_aliases: Vec::new() }, - ) - }) - .collect(); - AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } - }); - let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; - let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { - return; - }; - let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); - // Exclude impls that are directly on this type. They're already in the HTML. - // Some inlining scenarios can cause there to be two versions of the same - // impl: one on the type alias and one on the underlying target type. - let mut seen_impls: FxHashSet = cache - .impls - .get(&self_did) - .map(|s| &s[..]) - .unwrap_or_default() - .iter() - .map(|i| i.impl_item.item_id) - .collect(); - for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { - // Only include this impl if it actually unifies with this alias. - // Synthetic impls are not included; those are also included in the HTML. - // - // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this - // to use type unification. - // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress. - let Some(impl_did) = impl_item_id.as_def_id() else { continue }; - let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); - let reject_cx = DeepRejectCtxt::new(self.cx.tcx(), TreatParams::AsCandidateKey); - if !reject_cx.types_may_unify(aliased_ty, for_ty) { - continue; - } - // Avoid duplicates - if !seen_impls.insert(*impl_item_id) { - continue; + fn add_path(self: &Rc, path: &Path) { + let mut h = Rc::clone(&self); + let mut elems = path + .components() + .filter_map(|s| match s { + Component::Normal(s) => Some(s.to_owned()), + Component::ParentDir => Some(OsString::from("..")), + _ => None, + }) + .peekable(); + loop { + let cur_elem = elems.next().expect("empty file path"); + if cur_elem == ".." { + if let Some(parent) = h.parent.upgrade() { + h = parent; } - // This impl was not found in the set of rejected impls - aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone())); + continue; } - } - } - let mut type_impl_collector = TypeImplCollector { - aliased_types: IndexMap::default(), - visited_aliases: FxHashSet::default(), - cache, - cx, - }; - DocVisitor::visit_crate(&mut type_impl_collector, &krate); - // Final serialized form of the alias impl - struct AliasSerializableImpl { - text: String, - trait_: Option, - aliases: Vec, - } - impl Serialize for AliasSerializableImpl { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(None)?; - seq.serialize_element(&self.text)?; - if let Some(trait_) = &self.trait_ { - seq.serialize_element(trait_)?; + if elems.peek().is_none() { + h.elems.borrow_mut().insert(cur_elem); + break; } else { - seq.serialize_element(&0)?; - } - for type_ in &self.aliases { - seq.serialize_element(type_)?; + let entry = Rc::clone( + h.children + .borrow_mut() + .entry(cur_elem.clone()) + .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))), + ); + h = entry; } - seq.end() } } - let cx = type_impl_collector.cx; - let dst = cx.dst.join("type.impl"); - let aliased_types = type_impl_collector.aliased_types; - for aliased_type in aliased_types.values() { - let impls = aliased_type - .impl_ - .values() - .flat_map(|AliasedTypeImpl { impl_, type_aliases }| { - let mut ret = Vec::new(); - let trait_ = impl_ - .inner_impl() - .trait_ - .as_ref() - .map(|trait_| format!("{:#}", trait_.print(cx))); - // render_impl will filter out "impossible-to-call" methods - // to make that functionality work here, it needs to be called with - // each type alias, and if it gives a different result, split the impl - for &(type_alias_fqp, ref type_alias_item) in type_aliases { - let mut buf = Buffer::html(); - cx.id_map = Default::default(); - cx.deref_id_map = Default::default(); - let target_did = impl_ +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct TypeAlias; +type TypeAliasPart = Part; +impl CciPart for TypeAliasPart { + type FileFormat = sorted_template::Js; +} + +impl TypeAliasPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"(function() { + var type_impls = Object.fromEntries([", + r"]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()", + ) + } + + fn get( + cx: &mut Context<'_>, + krate: &Crate, + crate_name_json: &SortedJson, + ) -> Result, Error> { + let cache = &Rc::clone(&cx.shared).cache; + let mut path_parts = PartsAndLocations::default(); + + let mut type_impl_collector = TypeImplCollector { + aliased_types: IndexMap::default(), + visited_aliases: FxHashSet::default(), + cache, + cx, + }; + DocVisitor::visit_crate(&mut type_impl_collector, &krate); + let cx = type_impl_collector.cx; + let aliased_types = type_impl_collector.aliased_types; + for aliased_type in aliased_types.values() { + let impls = aliased_type + .impl_ + .values() + .flat_map(|AliasedTypeImpl { impl_, type_aliases }| { + let mut ret = Vec::new(); + let trait_ = impl_ .inner_impl() .trait_ .as_ref() - .map(|trait_| trait_.def_id()) - .or_else(|| impl_.inner_impl().for_.def_id(cache)); - let provided_methods; - let assoc_link = if let Some(target_did) = target_did { - provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx()); - AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods) - } else { - AssocItemLink::Anchor(None) - }; - super::render_impl( - &mut buf, - cx, - *impl_, - &type_alias_item, - assoc_link, - RenderMode::Normal, - None, - &[], - ImplRenderingParameters { - show_def_docs: true, - show_default_items: true, - show_non_assoc_items: true, - toggle_open_by_default: true, - }, - ); - let text = buf.into_inner(); - let type_alias_fqp = (*type_alias_fqp).iter().join("::"); - if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) { - ret.last_mut() - .expect("already established that ret.last() is Some()") - .aliases - .push(type_alias_fqp); + .map(|trait_| format!("{:#}", trait_.print(cx))); + // render_impl will filter out "impossible-to-call" methods + // to make that functionality work here, it needs to be called with + // each type alias, and if it gives a different result, split the impl + for &(type_alias_fqp, ref type_alias_item) in type_aliases { + let mut buf = Buffer::html(); + cx.id_map = Default::default(); + cx.deref_id_map = Default::default(); + let target_did = impl_ + .inner_impl() + .trait_ + .as_ref() + .map(|trait_| trait_.def_id()) + .or_else(|| impl_.inner_impl().for_.def_id(cache)); + let provided_methods; + let assoc_link = if let Some(target_did) = target_did { + provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx()); + AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods) + } else { + AssocItemLink::Anchor(None) + }; + super::render_impl( + &mut buf, + cx, + *impl_, + &type_alias_item, + assoc_link, + RenderMode::Normal, + None, + &[], + ImplRenderingParameters { + show_def_docs: true, + show_default_items: true, + show_non_assoc_items: true, + toggle_open_by_default: true, + }, + ); + let text = buf.into_inner(); + let type_alias_fqp = (*type_alias_fqp).iter().join("::"); + if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) { + ret.last_mut() + .expect("already established that ret.last() is Some()") + .aliases + .push(type_alias_fqp); + } else { + ret.push(AliasSerializableImpl { + text, + trait_: trait_.clone(), + aliases: vec![type_alias_fqp], + }) + } + } + ret + }) + .collect::>(); + + let mut path = PathBuf::from("type.impl"); + for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] { + path.push(component.as_str()); + } + let aliased_item_type = aliased_type.target_type; + path.push(&format!( + "{aliased_item_type}.{}.js", + aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] + )); + + let part = + SortedJson::array(impls.iter().map(SortedJson::serialize).collect::>()); + path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + } + Ok(path_parts) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct TraitAlias; +type TraitAliasPart = Part; +impl CciPart for TraitAliasPart { + type FileFormat = sorted_template::Js; +} + +impl TraitAliasPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"(function() { + var implementors = Object.fromEntries([", + r"]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()", + ) + } + + fn get( + cx: &mut Context<'_>, + crate_name_json: &SortedJson, + ) -> Result, Error> { + let cache = &cx.shared.cache; + let mut path_parts = PartsAndLocations::default(); + // Update the list of all implementors for traits + // + for (&did, imps) in &cache.implementors { + // Private modules can leak through to this phase of rustdoc, which + // could contain implementations for otherwise private types. In some + // rare cases we could find an implementation for an item which wasn't + // indexed, so we just skip this step in that case. + // + // FIXME: this is a vague explanation for why this can't be a `get`, in + // theory it should be... + let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { + Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { + Some((_, t)) => (p, t), + None => continue, + }, + None => match cache.external_paths.get(&did) { + Some((p, t)) => (p, t), + None => continue, + }, + }; + + let implementors = imps + .iter() + .filter_map(|imp| { + // If the trait and implementation are in the same crate, then + // there's no need to emit information about it (there's inlining + // going on). If they're in different crates then the crate defining + // the trait will be interested in our implementation. + // + // If the implementation is from another crate then that crate + // should add it. + if imp.impl_item.item_id.krate() == did.krate + || !imp.impl_item.item_id.is_local() + { + None } else { - ret.push(AliasSerializableImpl { - text, - trait_: trait_.clone(), - aliases: vec![type_alias_fqp], + Some(Implementor { + text: imp.inner_impl().print(false, cx).to_string(), + synthetic: imp.inner_impl().kind.is_auto(), + types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache), }) } - } - ret - }) - .collect::>(); + }) + .collect::>(); - // FIXME: this fixes only rustdoc part of instability of trait impls - // for js files, see #120371 - // Manually collect to string and sort to make list not depend on order - let mut impls = impls - .iter() - .map(|i| serde_json::to_string(i).expect("failed serde conversion")) - .collect::>(); - impls.sort(); + // Only create a js file if we have impls to add to it. If the trait is + // documented locally though we always create the file to avoid dead + // links. + if implementors.is_empty() && !cache.paths.contains_key(&did) { + continue; + } - let impls = format!(r#""{}":[{}]"#, krate.name(cx.tcx()), impls.join(",")); + let mut path = PathBuf::from("trait.impl"); + for component in &remote_path[..remote_path.len() - 1] { + path.push(component.as_str()); + } + path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - let mut mydst = dst.clone(); - for part in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] { - mydst.push(part.to_string()); + let part = SortedJson::array( + implementors.iter().map(SortedJson::serialize).collect::>(), + ); + path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); } - cx.shared.ensure_dir(&mydst)?; - let aliased_item_type = aliased_type.target_type; - mydst.push(&format!( - "{aliased_item_type}.{}.js", - aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] - )); - - let (mut all_impls, _) = try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); - all_impls.push(impls); - // Sort the implementors by crate so the file will be generated - // identically even with rustdoc running in parallel. - all_impls.sort(); - - let mut v = String::from("(function() {var type_impls = {\n"); - v.push_str(&all_impls.join(",\n")); - v.push_str("\n};"); - v.push_str( - "if (window.register_type_impls) {\ - window.register_type_impls(type_impls);\ - } else {\ - window.pending_type_impls = type_impls;\ - }", - ); - v.push_str("})()"); - cx.shared.fs.write(mydst, v)?; - } - - // Update the list of all implementors for traits - // - let dst = cx.dst.join("trait.impl"); - for (&did, imps) in &cache.implementors { - // Private modules can leak through to this phase of rustdoc, which - // could contain implementations for otherwise private types. In some - // rare cases we could find an implementation for an item which wasn't - // indexed, so we just skip this step in that case. - // - // FIXME: this is a vague explanation for why this can't be a `get`, in - // theory it should be... - let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { - Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { - Some((_, t)) => (p, t), - None => continue, - }, - None => match cache.external_paths.get(&did) { - Some((p, t)) => (p, t), - None => continue, - }, - }; + Ok(path_parts) + } +} - struct Implementor { - text: String, - synthetic: bool, - types: Vec, +struct Implementor { + text: String, + synthetic: bool, + types: Vec, +} + +impl Serialize for Implementor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + seq.serialize_element(&self.text)?; + if self.synthetic { + seq.serialize_element(&1)?; + seq.serialize_element(&self.types)?; } + seq.end() + } +} - impl Serialize for Implementor { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(None)?; - seq.serialize_element(&self.text)?; - if self.synthetic { - seq.serialize_element(&1)?; - seq.serialize_element(&self.types)?; - } - seq.end() +/// Collect the list of aliased types and their aliases. +/// +/// +/// The clean AST has type aliases that point at their types, but +/// this visitor works to reverse that: `aliased_types` is a map +/// from target to the aliases that reference it, and each one +/// will generate one file. +struct TypeImplCollector<'cx, 'cache> { + /// Map from DefId-of-aliased-type to its data. + aliased_types: IndexMap>, + visited_aliases: FxHashSet, + cache: &'cache Cache, + cx: &'cache mut Context<'cx>, +} + +/// Data for an aliased type. +/// +/// In the final file, the format will be roughly: +/// +/// ```json +/// // type.impl/CRATE/TYPENAME.js +/// JSONP( +/// "CRATE": [ +/// ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...], +/// ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...], +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType +/// ... +/// ] +/// ) +/// ``` +struct AliasedType<'cache> { + /// This is used to generate the actual filename of this aliased type. + target_fqp: &'cache [Symbol], + target_type: ItemType, + /// This is the data stored inside the file. + /// ItemId is used to deduplicate impls. + impl_: IndexMap>, +} + +/// The `impl_` contains data that's used to figure out if an alias will work, +/// and to generate the HTML at the end. +/// +/// The `type_aliases` list is built up with each type alias that matches. +struct AliasedTypeImpl<'cache> { + impl_: &'cache Impl, + type_aliases: Vec<(&'cache [Symbol], Item)>, +} + +impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> { + fn visit_item(&mut self, it: &Item) { + self.visit_item_recur(it); + let cache = self.cache; + let ItemKind::TypeAliasItem(ref t) = *it.kind else { return }; + let Some(self_did) = it.item_id.as_def_id() else { return }; + if !self.visited_aliases.insert(self_did) { + return; + } + let Some(target_did) = t.type_.def_id(cache) else { return }; + let get_extern = { || cache.external_paths.get(&target_did) }; + let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) + else { + return; + }; + let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| { + let impl_ = cache + .impls + .get(&target_did) + .map(|v| &v[..]) + .unwrap_or_default() + .iter() + .map(|impl_| { + (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() }) + }) + .collect(); + AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } + }); + let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; + let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { + return; + }; + let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); + // Exclude impls that are directly on this type. They're already in the HTML. + // Some inlining scenarios can cause there to be two versions of the same + // impl: one on the type alias and one on the underlying target type. + let mut seen_impls: FxHashSet = cache + .impls + .get(&self_did) + .map(|s| &s[..]) + .unwrap_or_default() + .iter() + .map(|i| i.impl_item.item_id) + .collect(); + for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { + // Only include this impl if it actually unifies with this alias. + // Synthetic impls are not included; those are also included in the HTML. + // + // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this + // to use type unification. + // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress. + let Some(impl_did) = impl_item_id.as_def_id() else { continue }; + let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); + let reject_cx = DeepRejectCtxt::new(self.cx.tcx(), TreatParams::AsCandidateKey); + if !reject_cx.types_may_unify(aliased_ty, for_ty) { + continue; + } + // Avoid duplicates + if !seen_impls.insert(*impl_item_id) { + continue; } + // This impl was not found in the set of rejected impls + aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone())); } + } +} - let implementors = imps - .iter() - .filter_map(|imp| { - // If the trait and implementation are in the same crate, then - // there's no need to emit information about it (there's inlining - // going on). If they're in different crates then the crate defining - // the trait will be interested in our implementation. - // - // If the implementation is from another crate then that crate - // should add it. - if imp.impl_item.item_id.krate() == did.krate || !imp.impl_item.item_id.is_local() { - None - } else { - Some(Implementor { - text: imp.inner_impl().print(false, cx).to_string(), - synthetic: imp.inner_impl().kind.is_auto(), - types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache), - }) - } - }) - .collect::>(); +/// Final serialized form of the alias impl +struct AliasSerializableImpl { + text: String, + trait_: Option, + aliases: Vec, +} - // Only create a js file if we have impls to add to it. If the trait is - // documented locally though we always create the file to avoid dead - // links. - if implementors.is_empty() && !cache.paths.contains_key(&did) { - continue; +impl Serialize for AliasSerializableImpl { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + seq.serialize_element(&self.text)?; + if let Some(trait_) = &self.trait_ { + seq.serialize_element(trait_)?; + } else { + seq.serialize_element(&0)?; + } + for type_ in &self.aliases { + seq.serialize_element(type_)?; } + seq.end() + } +} - // FIXME: this fixes only rustdoc part of instability of trait impls - // for js files, see #120371 - // Manually collect to string and sort to make list not depend on order - let mut implementors = implementors - .iter() - .map(|i| serde_json::to_string(i).expect("failed serde conversion")) - .collect::>(); - implementors.sort(); +/// Create all parents +fn create_parents(path: &Path) -> Result<(), Error> { + let parent = path.parent().expect("should not have an empty path here"); + try_err!(fs::create_dir_all(parent), parent); + Ok(()) +} - let implementors = format!(r#""{}":[{}]"#, krate.name(cx.tcx()), implementors.join(",")); +/// Create parents and then write +fn write_create_parents(path: &Path, content: String) -> Result<(), Error> { + create_parents(path)?; + try_err!(fs::write(path, content), path); + Ok(()) +} - let mut mydst = dst.clone(); - for part in &remote_path[..remote_path.len() - 1] { - mydst.push(part.to_string()); +/// Returns a blank template unless we could find one to append to +fn read_template_or_blank( + mut make_blank: F, + path: &Path, +) -> Result, Error> +where + F: FnMut() -> SortedTemplate, +{ + match fs::read_to_string(&path) { + Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()), + Err(e) => Err(Error::new(e, &path)), + } +} + +/// info from this crate and the --include-info-json'd crates +fn write_rendered_cci( + mut make_blank: F, + dst: &Path, + crates_info: &[CrateInfo], +) -> Result<(), Error> +where + F: FnMut() -> SortedTemplate, +{ + // read parts from disk + let path_parts = + crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten(); + // read previous rendered cci from storage, append to them + let mut templates: FxHashMap> = Default::default(); + for (path, part) in path_parts { + let part = format!("{part}"); + let path = dst.join(&path); + match templates.entry(path.clone()) { + Entry::Vacant(entry) => { + let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?; + let template = entry.insert(template); + template.append(part); + } + Entry::Occupied(mut t) => t.get_mut().append(part), } - cx.shared.ensure_dir(&mydst)?; - mydst.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - - let (mut all_implementors, _) = - try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); - all_implementors.push(implementors); - // Sort the implementors by crate so the file will be generated - // identically even with rustdoc running in parallel. - all_implementors.sort(); - - let mut v = String::from("(function() {var implementors = {\n"); - v.push_str(&all_implementors.join(",\n")); - v.push_str("\n};"); - v.push_str( - "if (window.register_implementors) {\ - window.register_implementors(implementors);\ - } else {\ - window.pending_implementors = implementors;\ - }", - ); - v.push_str("})()"); - cx.shared.fs.write(mydst, v)?; + } + + // write the merged cci to disk + for (path, template) in templates { + create_parents(&path)?; + let file = try_err!(File::create(&path), &path); + let mut file = BufWriter::new(file); + try_err!(write!(file, "{template}"), &path); + try_err!(file.flush(), &path); } Ok(()) } From f1a996c39b4accf958cb02028e9b8e5d4e3d796d Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Thu, 25 Jul 2024 00:05:32 +0000 Subject: [PATCH 065/685] add blank line, remove extraneous comment --- src/librustdoc/config.rs | 1 + src/librustdoc/html/render/context.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 2e54a22840bb5..e4549796b3e83 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -730,6 +730,7 @@ impl Options { let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); + if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { dcx.fatal( "--generate-link-to-definition option can only be used with HTML output format", diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 8e72dd6a864ae..0ed8921b1e8d7 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -722,7 +722,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ); shared.fs.write(help_file, v)?; - // if to avoid writing files to doc root unless we're on the final invocation if shared.layout.scrape_examples_extension { page.title = "About scraped examples"; page.description = "How the scraped examples feature works in Rustdoc"; From 17c89239d92eca1e4e22ff6ba9d1be0f2f7b36fc Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Wed, 24 Jul 2024 23:36:49 +0000 Subject: [PATCH 066/685] move sorted_template and sorted_json tests --- src/librustdoc/clean/types.rs | 2 +- .../html/render/sorted_json/tests.rs | 119 ++++++++ src/librustdoc/html/render/sorted_template.rs | 3 + .../html/render/sorted_template/tests.rs | 148 ++++++++++ src/librustdoc/html/render/tests.rs | 271 ------------------ 5 files changed, 271 insertions(+), 272 deletions(-) create mode 100644 src/librustdoc/html/render/sorted_json/tests.rs create mode 100644 src/librustdoc/html/render/sorted_template/tests.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 542e810b5cfa8..4850500a1bfae 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -128,7 +128,7 @@ pub(crate) struct ExternalCrate { } impl ExternalCrate { - pub(crate) const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; + const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; #[inline] pub(crate) fn def_id(&self) -> DefId { diff --git a/src/librustdoc/html/render/sorted_json/tests.rs b/src/librustdoc/html/render/sorted_json/tests.rs new file mode 100644 index 0000000000000..1e72c6f614c38 --- /dev/null +++ b/src/librustdoc/html/render/sorted_json/tests.rs @@ -0,0 +1,119 @@ +use super::super::sorted_json::*; + +fn check(json: SortedJson, serialized: &str) { + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = json.to_string(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = serde_json::to_string(&json).unwrap(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); +} + +// Test this basic are needed because we are testing that our Display impl + serialize impl don't +// nest everything in extra level of string. We also are testing round trip. +#[test] +fn escape_json_number() { + let json = SortedJson::serialize(3); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), "3"); +} + +#[test] +fn escape_json_single_quote() { + let json = SortedJson::serialize("he's"); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\'s""#); +} + +#[test] +fn escape_json_array() { + let json = SortedJson::serialize([1, 2, 3]); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#"[1,2,3]"#); +} + +#[test] +fn escape_json_string() { + let json = SortedJson::serialize(r#"he"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\"llo""#); +} + +#[test] +fn escape_json_string_escaped() { + let json = SortedJson::serialize(r#"he\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); +} + +#[test] +fn escape_json_string_escaped_escaped() { + let json = SortedJson::serialize(r#"he\\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); +} + +#[test] +fn number() { + let json = SortedJson::serialize(3); + let serialized = "3"; + check(json, serialized); +} + +#[test] +fn boolean() { + let json = SortedJson::serialize(true); + let serialized = "true"; + check(json, serialized); +} + +#[test] +fn string() { + let json = SortedJson::serialize("he\"llo"); + let serialized = r#""he\"llo""#; + check(json, serialized); +} + +#[test] +fn serialize_array() { + let json = SortedJson::serialize([3, 1, 2]); + let serialized = "[3,1,2]"; + check(json, serialized); +} + +#[test] +fn sorted_array() { + let items = ["c", "a", "b"]; + let serialized = r#"["a","b","c"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array(items); + check(json, serialized); +} + +#[test] +fn nested_array() { + let a = SortedJson::serialize(3); + let b = SortedJson::serialize(2); + let c = SortedJson::serialize(1); + let d = SortedJson::serialize([1, 3, 2]); + let json = SortedJson::array([a, b, c, d]); + let serialized = r#"[1,2,3,[1,3,2]]"#; + check(json, serialized); +} + +#[test] +fn array_unsorted() { + let items = ["c", "a", "b"]; + let serialized = r#"["c","a","b"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array_unsorted(items); + check(json, serialized); +} diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 95240616b01dc..8e0a2ee0fd4d9 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -134,3 +134,6 @@ impl fmt::Display for Error { write!(f, "invalid template") } } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/sorted_template/tests.rs b/src/librustdoc/html/render/sorted_template/tests.rs new file mode 100644 index 0000000000000..04553f65a2154 --- /dev/null +++ b/src/librustdoc/html/render/sorted_template/tests.rs @@ -0,0 +1,148 @@ +use super::super::sorted_template::*; +use std::str::FromStr; + +fn is_comment_js(s: &str) -> bool { + s.starts_with("//") +} + +fn is_comment_html(s: &str) -> bool { + // not correct but good enough for these tests + s.starts_with("") +} + +#[test] +fn html_from_empty() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "

    hello

    kind

    world

    "); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn html_page() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_from_empty() { + let inserts = ["1", "2", "2", "2", "3", "1"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3"); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_empty_array() { + let template = SortedTemplate::::before_after("[", "]"); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_number_array() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1,2,3]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn magic_js_number_array() { + let inserts = ["1", "1"]; + let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn round_trip_js() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "[1,2,3,4]"); + assert!(is_comment_js(end)); +} + +#[test] +fn round_trip_html() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + template.append(inserts[0].to_string()); + template.append(inserts[1].to_string()); + let template = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + template.append(inserts[2].to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}

    hello

    kind

    world

    {after}")); + assert!(is_comment_html(end)); +} + +#[test] +fn blank_js() { + let inserts = ["1", "2", "3"]; + let template = SortedTemplate::::before_after("", ""); + let template = format!("{template}"); + let (t, _) = template.rsplit_once("\n").unwrap(); + assert_eq!(t, ""); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3,4"); + assert!(is_comment_js(end)); +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 16e67b0f1180e..4a9724a6f840f 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -52,274 +52,3 @@ fn test_all_types_prints_header_once() { assert_eq!(1, buffer.into_inner().matches("List of all items").count()); } - -mod sorted_json { - use super::super::sorted_json::*; - - fn check(json: SortedJson, serialized: &str) { - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - - let json = json.to_string(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); - - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - - let json = serde_json::to_string(&json).unwrap(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); - - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - } - - #[test] - fn escape_json_number() { - let json = SortedJson::serialize(3); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), "3"); - } - - #[test] - fn escape_json_single_quote() { - let json = SortedJson::serialize("he's"); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\'s""#); - } - - #[test] - fn escape_json_array() { - let json = SortedJson::serialize([1, 2, 3]); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#"[1,2,3]"#); - } - - #[test] - fn escape_json_string() { - let json = SortedJson::serialize(r#"he"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\"llo""#); - } - - #[test] - fn escape_json_string_escaped() { - let json = SortedJson::serialize(r#"he\"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); - } - - #[test] - fn escape_json_string_escaped_escaped() { - let json = SortedJson::serialize(r#"he\\"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); - } - - #[test] - fn number() { - let json = SortedJson::serialize(3); - let serialized = "3"; - check(json, serialized); - } - - #[test] - fn boolean() { - let json = SortedJson::serialize(true); - let serialized = "true"; - check(json, serialized); - } - - #[test] - fn string() { - let json = SortedJson::serialize("he\"llo"); - let serialized = r#""he\"llo""#; - check(json, serialized); - } - - #[test] - fn serialize_array() { - let json = SortedJson::serialize([3, 1, 2]); - let serialized = "[3,1,2]"; - check(json, serialized); - } - - #[test] - fn sorted_array() { - let items = ["c", "a", "b"]; - let serialized = r#"["a","b","c"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array(items); - check(json, serialized); - } - - #[test] - fn nested_array() { - let a = SortedJson::serialize(3); - let b = SortedJson::serialize(2); - let c = SortedJson::serialize(1); - let d = SortedJson::serialize([1, 3, 2]); - let json = SortedJson::array([a, b, c, d]); - let serialized = r#"[1,2,3,[1,3,2]]"#; - check(json, serialized); - } - - #[test] - fn array_unsorted() { - let items = ["c", "a", "b"]; - let serialized = r#"["c","a","b"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array_unsorted(items); - check(json, serialized); - } -} - -mod sorted_template { - use super::super::sorted_template::*; - use std::str::FromStr; - - fn is_comment_js(s: &str) -> bool { - s.starts_with("//") - } - - fn is_comment_html(s: &str) -> bool { - // not correct but good enough for these tests - s.starts_with("") - } - - #[test] - fn html_from_empty() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; - let mut template = SortedTemplate::::before_after("", ""); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "

    hello

    kind

    world

    "); - assert!(is_comment_html(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn html_page() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; - let before = ""; - let after = ""; - let mut template = SortedTemplate::::before_after(before, after); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); - assert!(is_comment_html(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_from_empty() { - let inserts = ["1", "2", "2", "2", "3", "1"]; - let mut template = SortedTemplate::::before_after("", ""); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "1,2,3"); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_empty_array() { - let template = SortedTemplate::::before_after("[", "]"); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_number_array() { - let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[1,2,3]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn magic_js_number_array() { - let inserts = ["1", "1"]; - let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[1]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn round_trip_js() { - let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); - for insert in inserts { - template.append(insert.to_string()); - } - let template1 = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template1).unwrap(); - assert_eq!(template1, format!("{template}")); - template.append("4".to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "[1,2,3,4]"); - assert!(is_comment_js(end)); - } - - #[test] - fn round_trip_html() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; - let before = ""; - let after = ""; - let mut template = SortedTemplate::::before_after(before, after); - template.append(inserts[0].to_string()); - template.append(inserts[1].to_string()); - let template = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template).unwrap(); - template.append(inserts[2].to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("{before}

    hello

    kind

    world

    {after}")); - assert!(is_comment_html(end)); - } - - #[test] - fn blank_js() { - let inserts = ["1", "2", "3"]; - let template = SortedTemplate::::before_after("", ""); - let template = format!("{template}"); - let (t, _) = template.rsplit_once("\n").unwrap(); - assert_eq!(t, ""); - let mut template = SortedTemplate::::from_str(&template).unwrap(); - for insert in inserts { - template.append(insert.to_string()); - } - let template1 = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template1).unwrap(); - assert_eq!(template1, format!("{template}")); - template.append("4".to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "1,2,3,4"); - assert!(is_comment_js(end)); - } -} From 67663fc680428cf22267f974106d2805008a8568 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Thu, 25 Jul 2024 15:27:29 +0000 Subject: [PATCH 067/685] added unit tests for write_shared --- src/librustdoc/html/render/sorted_json.rs | 3 + src/librustdoc/html/render/write_shared.rs | 99 ++++----- .../html/render/write_shared/tests.rs | 206 ++++++++++++++++++ 3 files changed, 257 insertions(+), 51 deletions(-) create mode 100644 src/librustdoc/html/render/write_shared/tests.rs diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/sorted_json.rs index 3a097733b8b20..e937382f5b0a6 100644 --- a/src/librustdoc/html/render/sorted_json.rs +++ b/src/librustdoc/html/render/sorted_json.rs @@ -80,3 +80,6 @@ impl fmt::Display for EscapedJson { write!(f, "{}", json) } } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index eaebeadd8817e..c2d2b4cd7d9fb 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -15,7 +15,6 @@ use std::any::Any; use std::cell::RefCell; -use std::collections::hash_map::Entry; use std::ffi::OsString; use std::fs::File; use std::io::BufWriter; @@ -52,7 +51,7 @@ use crate::html::layout; use crate::html::render::search_index::build_index; use crate::html::render::search_index::SerializedSearchIndex; use crate::html::render::sorted_json::{EscapedJson, SortedJson}; -use crate::html::render::sorted_template::{self, SortedTemplate}; +use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; @@ -78,33 +77,29 @@ pub(crate) fn write_shared( let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand let crate_name_json = SortedJson::serialize(crate_name); // "rand" - let external_crates = hack_get_external_crate_names(cx)?; + let external_crates = hack_get_external_crate_names(&cx.dst)?; let info = CrateInfo { src_files_js: SourcesPart::get(cx, &crate_name_json)?, - search_index_js: SearchIndexPart::get(cx, index)?, + search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?, all_crates: AllCratesPart::get(crate_name_json.clone())?, crates_index: CratesIndexPart::get(&crate_name, &external_crates)?, trait_impl: TraitAliasPart::get(cx, &crate_name_json)?, type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?, }; - let crates_info = vec![info]; // we have info from just one crate + let crates = vec![info]; // we have info from just one crate. rest will found in out dir write_static_files(cx, &opt)?; let dst = &cx.dst; if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { if cx.include_sources { - write_rendered_cci::(SourcesPart::blank, dst, &crates_info)?; + write_rendered_cci::(SourcesPart::blank, dst, &crates)?; } - write_rendered_cci::( - SearchIndexPart::blank, - dst, - &crates_info, - )?; - write_rendered_cci::(AllCratesPart::blank, dst, &crates_info)?; - } - write_rendered_cci::(TraitAliasPart::blank, dst, &crates_info)?; - write_rendered_cci::(TypeAliasPart::blank, dst, &crates_info)?; + write_rendered_cci::(SearchIndexPart::blank, dst, &crates)?; + write_rendered_cci::(AllCratesPart::blank, dst, &crates)?; + } + write_rendered_cci::(TraitAliasPart::blank, dst, &crates)?; + write_rendered_cci::(TypeAliasPart::blank, dst, &crates)?; match &opt.index_page { Some(index_page) if opt.enable_index_page => { let mut md_opts = opt.clone(); @@ -119,7 +114,7 @@ pub(crate) fn write_shared( write_rendered_cci::( || CratesIndexPart::blank(cx), dst, - &crates_info, + &crates, )?; } _ => {} // they don't want an index page @@ -189,7 +184,8 @@ fn write_search_desc( let path = path.join(filename); let part = SortedJson::serialize(&part); let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); - write_create_parents(&path, part)?; + create_parents(&path)?; + try_err!(fs::write(&path, part), &path); } Ok(()) } @@ -286,8 +282,11 @@ else if (window.initSearch) window.initSearch(searchIndex);", ) } - fn get(cx: &Context<'_>, search_index: SortedJson) -> Result, Error> { - let path = suffix_path("search-index.js", &cx.shared.resource_suffix); + fn get( + search_index: SortedJson, + resource_suffix: &str, + ) -> Result, Error> { + let path = suffix_path("search-index.js", resource_suffix); let search_index = EscapedJson::from(search_index); Ok(PartsAndLocations::with(path, search_index)) } @@ -319,8 +318,8 @@ impl AllCratesPart { /// /// This is to match the current behavior of rustdoc, which allows you to get all crates /// on the index page, even if --enable-index-page is only passed to the last crate. -fn hack_get_external_crate_names(cx: &Context<'_>) -> Result, Error> { - let path = cx.dst.join("crates.js"); +fn hack_get_external_crate_names(doc_root: &Path) -> Result, Error> { + let path = doc_root.join("crates.js"); let Ok(content) = fs::read_to_string(&path) else { // they didn't emit invocation specific, so we just say there were no crates return Ok(Vec::default()); @@ -361,7 +360,7 @@ impl CratesIndexPart { match SortedTemplate::magic(&template, MAGIC) { Ok(template) => template, Err(e) => panic!( - "{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page" + "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}" ), } } @@ -860,6 +859,21 @@ impl Serialize for AliasSerializableImpl { } } +fn get_path_parts( + dst: &Path, + crates_info: &[CrateInfo], +) -> FxHashMap> { + let mut templates: FxHashMap> = FxHashMap::default(); + crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each( + |(path, part)| { + let path = dst.join(&path); + let part = part.to_string(); + templates.entry(path).or_default().push(part); + }, + ); + templates +} + /// Create all parents fn create_parents(path: &Path) -> Result<(), Error> { let parent = path.parent().expect("should not have an empty path here"); @@ -867,20 +881,13 @@ fn create_parents(path: &Path) -> Result<(), Error> { Ok(()) } -/// Create parents and then write -fn write_create_parents(path: &Path, content: String) -> Result<(), Error> { - create_parents(path)?; - try_err!(fs::write(path, content), path); - Ok(()) -} - /// Returns a blank template unless we could find one to append to -fn read_template_or_blank( +fn read_template_or_blank( mut make_blank: F, path: &Path, -) -> Result, Error> +) -> Result, Error> where - F: FnMut() -> SortedTemplate, + F: FnMut() -> SortedTemplate, { match fs::read_to_string(&path) { Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)), @@ -898,27 +905,14 @@ fn write_rendered_cci( where F: FnMut() -> SortedTemplate, { - // read parts from disk - let path_parts = - crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten(); - // read previous rendered cci from storage, append to them - let mut templates: FxHashMap> = Default::default(); - for (path, part) in path_parts { - let part = format!("{part}"); - let path = dst.join(&path); - match templates.entry(path.clone()) { - Entry::Vacant(entry) => { - let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?; - let template = entry.insert(template); - template.append(part); - } - Entry::Occupied(mut t) => t.get_mut().append(part), - } - } - // write the merged cci to disk - for (path, template) in templates { + for (path, parts) in get_path_parts::(dst, crates_info) { create_parents(&path)?; + // read previous rendered cci from storage, append to them + let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?; + for part in parts { + template.append(part); + } let file = try_err!(File::create(&path), &path); let mut file = BufWriter::new(file); try_err!(write!(file, "{template}"), &path); @@ -926,3 +920,6 @@ where } Ok(()) } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs new file mode 100644 index 0000000000000..000e233aec00f --- /dev/null +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -0,0 +1,206 @@ +use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::sorted_template::{Html, SortedTemplate}; +use crate::html::render::write_shared::*; + +#[test] +fn hack_external_crate_names() { + let path = tempfile::TempDir::new().unwrap(); + let path = path.path(); + let crates = hack_get_external_crate_names(&path).unwrap(); + assert!(crates.is_empty()); + fs::write(path.join("crates.js"), r#"window.ALL_CRATES = ["a","b","c"];"#).unwrap(); + let crates = hack_get_external_crate_names(&path).unwrap(); + assert_eq!(crates, ["a".to_string(), "b".to_string(), "c".to_string()]); +} + +fn but_last_line(s: &str) -> &str { + let (before, _) = s.rsplit_once("\n").unwrap(); + before +} + +#[test] +fn sources_template() { + let mut template = SourcesPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r"var srcIndex = new Map(JSON.parse('[]')); +createSrcSidebar();" + ); + template.append(EscapedJson::from(SortedJson::serialize("u")).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"var srcIndex = new Map(JSON.parse('["u"]')); +createSrcSidebar();"# + ); + template.append(EscapedJson::from(SortedJson::serialize("v")).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"var srcIndex = new Map(JSON.parse('["u","v"]')); +createSrcSidebar();"# + ); +} + +#[test] +fn sources_parts() { + let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap(); + assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js")); + assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#); +} + +#[test] +fn all_crates_template() { + let mut template = AllCratesPart::blank(); + assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];"); + template.append(EscapedJson::from(SortedJson::serialize("b")).to_string()); + assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#); + template.append(EscapedJson::from(SortedJson::serialize("a")).to_string()); + assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#); +} + +#[test] +fn all_crates_parts() { + let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap(); + assert_eq!(&parts.parts[0].0, Path::new("crates.js")); + assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#); +} + +#[test] +fn search_index_template() { + let mut template = SearchIndexPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); + template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[[1,2]]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); + template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); +} + +#[test] +fn crates_index_part() { + let external_crates = ["bar".to_string(), "baz".to_string()]; + let mut parts = CratesIndexPart::get("foo", &external_crates).unwrap(); + parts.parts.sort_by(|a, b| a.1.to_string().cmp(&b.1.to_string())); + + assert_eq!(&parts.parts[0].0, Path::new("index.html")); + assert_eq!(&parts.parts[0].1.to_string(), r#"
  • bar
  • "#); + + assert_eq!(&parts.parts[1].0, Path::new("index.html")); + assert_eq!(&parts.parts[1].1.to_string(), r#"
  • baz
  • "#); + + assert_eq!(&parts.parts[2].0, Path::new("index.html")); + assert_eq!(&parts.parts[2].1.to_string(), r#"
  • foo
  • "#); +} + +#[test] +fn trait_alias_template() { + let mut template = TraitAliasPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); + template.append(SortedJson::serialize(["a"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([["a"]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); + template.append(SortedJson::serialize(["b"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([["a"],["b"]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); +} + +#[test] +fn type_alias_template() { + let mut template = TypeAliasPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); + template.append(SortedJson::serialize(["a"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([["a"]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); + template.append(SortedJson::serialize(["b"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([["a"],["b"]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); +} + +#[test] +fn read_template_test() { + let path = tempfile::TempDir::new().unwrap(); + let path = path.path().join("file.html"); + let make_blank = || SortedTemplate::::before_after("
    ", "
    "); + + let template = read_template_or_blank(make_blank, &path).unwrap(); + assert_eq!(but_last_line(&template.to_string()), "
    "); + fs::write(&path, template.to_string()).unwrap(); + let mut template = read_template_or_blank(make_blank, &path).unwrap(); + template.append("".to_string()); + fs::write(&path, template.to_string()).unwrap(); + let mut template = read_template_or_blank(make_blank, &path).unwrap(); + template.append("
    ".to_string()); + fs::write(&path, template.to_string()).unwrap(); + let template = read_template_or_blank(make_blank, &path).unwrap(); + + assert_eq!(but_last_line(&template.to_string()), "

    "); +} From 4b418cd4aa9b2b8c52e8029c13b0530abf97bce7 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Fri, 26 Jul 2024 17:09:32 +0000 Subject: [PATCH 068/685] rename sortedjson -> orderedjson --- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/html/render/mod.rs | 2 +- .../{sorted_json.rs => ordered_json.rs} | 56 +++---- .../{sorted_json => ordered_json}/tests.rs | 52 +++---- src/librustdoc/html/render/search_index.rs | 10 +- src/librustdoc/html/render/sorted_template.rs | 108 ++++++++------ .../html/render/sorted_template/tests.rs | 21 +-- src/librustdoc/html/render/write_shared.rs | 141 ++++++++++-------- .../html/render/write_shared/tests.rs | 29 ++-- 9 files changed, 228 insertions(+), 193 deletions(-) rename src/librustdoc/html/render/{sorted_json.rs => ordered_json.rs} (55%) rename src/librustdoc/html/render/{sorted_json => ordered_json}/tests.rs (55%) diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 67ba8c773175c..b3fccbf6456e0 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -16,7 +16,7 @@ minifier = "0.3.0" pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -serde_json = { version = "1.0", features = ["preserve_order"] } +serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 4b1c9b4af474a..586c3c509b4ce 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -29,9 +29,9 @@ pub(crate) mod search_index; mod tests; mod context; +mod ordered_json; mod print_item; pub(crate) mod sidebar; -mod sorted_json; mod sorted_template; mod span_map; mod type_layout; diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/ordered_json.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json.rs rename to src/librustdoc/html/render/ordered_json.rs index e937382f5b0a6..3f76ff659d04e 100644 --- a/src/librustdoc/html/render/sorted_json.rs +++ b/src/librustdoc/html/render/ordered_json.rs @@ -1,72 +1,74 @@ +use std::borrow::Borrow; +use std::fmt; + use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::borrow::Borrow; -use std::fmt; /// Prerenedered json. /// -/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified -/// keys. -/// -/// Must use serde_json with the preserve_order feature. -/// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(from = "Value")] #[serde(into = "Value")] -pub(crate) struct SortedJson(String); +pub(crate) struct OrderedJson(String); -impl SortedJson { +impl OrderedJson { /// If you pass in an array, it will not be sorted. - pub(crate) fn serialize(item: T) -> Self { - SortedJson(serde_json::to_string(&item).unwrap()) + pub(crate) fn serialize(item: T) -> Result { + Ok(OrderedJson(serde_json::to_string(&item)?)) } /// Serializes and sorts - pub(crate) fn array, I: IntoIterator>(items: I) -> Self { + pub(crate) fn array_sorted, I: IntoIterator>( + items: I, + ) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{}]", items)) + OrderedJson(format!("[{}]", items)) } - pub(crate) fn array_unsorted, I: IntoIterator>( + pub(crate) fn array_unsorted, I: IntoIterator>( items: I, ) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{items}]")) + OrderedJson(format!("[{items}]")) } } -impl fmt::Display for SortedJson { +impl fmt::Display for OrderedJson { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + self.0.fmt(f) } } -impl From for SortedJson { +impl From for OrderedJson { fn from(value: Value) -> Self { - SortedJson(serde_json::to_string(&value).unwrap()) + let serialized = + serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); + OrderedJson(serialized) } } -impl From for Value { - fn from(json: SortedJson) -> Self { - serde_json::from_str(&json.0).unwrap() +impl From for Value { + fn from(json: OrderedJson) -> Self { + serde_json::from_str(&json.0).expect("OrderedJson should always store valid JSON") } } /// For use in JSON.parse('{...}'). /// -/// JSON.parse supposedly loads faster than raw JS source, +/// Assumes we are going to be wrapped in single quoted strings. +/// +/// JSON.parse loads faster than raw JS source, /// so this is used for large objects. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct EscapedJson(SortedJson); +pub(crate) struct EscapedJson(OrderedJson); -impl From for EscapedJson { - fn from(json: SortedJson) -> Self { +impl From for EscapedJson { + fn from(json: OrderedJson) -> Self { EscapedJson(json) } } @@ -77,7 +79,7 @@ impl fmt::Display for EscapedJson { // for JSON content. // We need to escape double quotes for the JSON let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); - write!(f, "{}", json) + json.fmt(f) } } diff --git a/src/librustdoc/html/render/sorted_json/tests.rs b/src/librustdoc/html/render/ordered_json/tests.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json/tests.rs rename to src/librustdoc/html/render/ordered_json/tests.rs index 1e72c6f614c38..e0fe6446b9aff 100644 --- a/src/librustdoc/html/render/sorted_json/tests.rs +++ b/src/librustdoc/html/render/ordered_json/tests.rs @@ -1,90 +1,90 @@ -use super::super::sorted_json::*; +use super::super::ordered_json::*; -fn check(json: SortedJson, serialized: &str) { +fn check(json: OrderedJson, serialized: &str) { assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = json.to_string(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = serde_json::to_string(&json).unwrap(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); } -// Test this basic are needed because we are testing that our Display impl + serialize impl don't -// nest everything in extra level of string. We also are testing round trip. +// Make sure there is no extra level of string, plus number of escapes. #[test] fn escape_json_number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), "3"); } #[test] fn escape_json_single_quote() { - let json = SortedJson::serialize("he's"); + let json = OrderedJson::serialize("he's").unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\'s""#); } #[test] fn escape_json_array() { - let json = SortedJson::serialize([1, 2, 3]); + let json = OrderedJson::serialize([1, 2, 3]).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#"[1,2,3]"#); } #[test] fn escape_json_string() { - let json = SortedJson::serialize(r#"he"llo"#); + let json = OrderedJson::serialize(r#"he"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\"llo""#); } #[test] fn escape_json_string_escaped() { - let json = SortedJson::serialize(r#"he\"llo"#); + let json = OrderedJson::serialize(r#"he\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); } #[test] fn escape_json_string_escaped_escaped() { - let json = SortedJson::serialize(r#"he\\"llo"#); + let json = OrderedJson::serialize(r#"he\\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); } +// Testing round trip + making sure there is no extra level of string #[test] fn number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let serialized = "3"; check(json, serialized); } #[test] fn boolean() { - let json = SortedJson::serialize(true); + let json = OrderedJson::serialize(true).unwrap(); let serialized = "true"; check(json, serialized); } #[test] fn string() { - let json = SortedJson::serialize("he\"llo"); + let json = OrderedJson::serialize("he\"llo").unwrap(); let serialized = r#""he\"llo""#; check(json, serialized); } #[test] fn serialize_array() { - let json = SortedJson::serialize([3, 1, 2]); + let json = OrderedJson::serialize([3, 1, 2]).unwrap(); let serialized = "[3,1,2]"; check(json, serialized); } @@ -93,18 +93,19 @@ fn serialize_array() { fn sorted_array() { let items = ["c", "a", "b"]; let serialized = r#"["a","b","c"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_sorted(items); check(json, serialized); } #[test] fn nested_array() { - let a = SortedJson::serialize(3); - let b = SortedJson::serialize(2); - let c = SortedJson::serialize(1); - let d = SortedJson::serialize([1, 3, 2]); - let json = SortedJson::array([a, b, c, d]); + let a = OrderedJson::serialize(3).unwrap(); + let b = OrderedJson::serialize(2).unwrap(); + let c = OrderedJson::serialize(1).unwrap(); + let d = OrderedJson::serialize([1, 3, 2]).unwrap(); + let json = OrderedJson::array_sorted([a, b, c, d]); let serialized = r#"[1,2,3,[1,3,2]]"#; check(json, serialized); } @@ -113,7 +114,8 @@ fn nested_array() { fn array_unsorted() { let items = ["c", "a", "b"]; let serialized = r#"["c","a","b"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array_unsorted(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_unsorted(items); check(json, serialized); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 184e5afba3c99..8a12bdef69bf4 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -18,7 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; -use crate::html::render::sorted_json::SortedJson; +use crate::html::render::ordered_json::OrderedJson; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// The serialized search description sharded version @@ -47,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features pub(crate) struct SerializedSearchIndex { - pub(crate) index: SortedJson, + pub(crate) index: OrderedJson, pub(crate) desc: Vec<(usize, String)>, } @@ -693,9 +693,9 @@ pub(crate) fn build_index<'tcx>( desc_index, empty_desc, }; - let index = SortedJson::array_unsorted([ - SortedJson::serialize(crate_name.as_str()), - SortedJson::serialize(data), + let index = OrderedJson::array_unsorted([ + OrderedJson::serialize(crate_name.as_str()).unwrap(), + OrderedJson::serialize(data).unwrap(), ]); SerializedSearchIndex { index, desc } } diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 8e0a2ee0fd4d9..1dc70408f013d 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -1,8 +1,9 @@ use std::collections::BTreeSet; -use std::fmt; +use std::fmt::{self, Write as _}; use std::marker::PhantomData; use std::str::FromStr; +use itertools::{Itertools as _, Position}; use serde::{Deserialize, Serialize}; /// Append-only templates for sorted, deduplicated lists of items. @@ -13,7 +14,7 @@ pub(crate) struct SortedTemplate { format: PhantomData, before: String, after: String, - contents: BTreeSet, + fragments: BTreeSet, } /// Written to last line of file to specify the location of each fragment @@ -22,82 +23,88 @@ struct Offset { /// Index of the first byte in the template start: usize, /// The length of each fragment in the encoded template, including the separator - delta: Vec, + fragment_lengths: Vec, } impl SortedTemplate { /// Generate this template from arbitary text. /// Will insert wherever the substring `magic` can be found. /// Errors if it does not appear exactly once. - pub(crate) fn magic(template: &str, magic: &str) -> Result { - let mut split = template.split(magic); - let before = split.next().ok_or(Error)?; - let after = split.next().ok_or(Error)?; + pub(crate) fn from_template(template: &str, delimiter: &str) -> Result { + let mut split = template.split(delimiter); + let before = split.next().ok_or(Error("delimiter should appear at least once"))?; + let after = split.next().ok_or(Error("delimiter should appear at least once"))?; + // not `split_once` because we want to check for too many occurrences if split.next().is_some() { - return Err(Error); + return Err(Error("delimiter should appear at most once")); } - Ok(Self::before_after(before, after)) + Ok(Self::from_before_after(before, after)) } - /// Template will insert contents between `before` and `after` - pub(crate) fn before_after(before: S, after: T) -> Self { + /// Template will insert fragments between `before` and `after` + pub(crate) fn from_before_after(before: S, after: T) -> Self { let before = before.to_string(); let after = after.to_string(); - SortedTemplate { format: PhantomData, before, after, contents: Default::default() } + SortedTemplate { format: PhantomData, before, after, fragments: Default::default() } } } -impl SortedTemplate { +impl SortedTemplate { /// Adds this text to the template pub(crate) fn append(&mut self, insert: String) { - self.contents.insert(insert); + self.fragments.insert(insert); } } impl fmt::Display for SortedTemplate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut delta = Vec::default(); + fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fragment_lengths = Vec::default(); write!(f, "{}", self.before)?; - let contents: Vec<_> = self.contents.iter().collect(); - let mut sep = ""; - for content in contents { - delta.push(sep.len() + content.len()); - write!(f, "{}{}", sep, content)?; - sep = F::SEPARATOR; + for (p, fragment) in self.fragments.iter().with_position() { + let mut f = DeltaWriter { inner: &mut f, delta: 0 }; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + write!(f, "{}{}", sep, fragment)?; + fragment_lengths.push(f.delta); } - let offset = Offset { start: self.before.len(), delta }; + let offset = Offset { start: self.before.len(), fragment_lengths }; let offset = serde_json::to_string(&offset).unwrap(); - write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?; - Ok(()) + write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END) } } -fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> { - s.is_char_boundary(index).then(|| s.split_at(index)) -} - impl FromStr for SortedTemplate { type Err = Error; fn from_str(s: &str) -> Result { - let (s, offset) = s.rsplit_once("\n").ok_or(Error)?; - let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?; - let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?; - let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?; - let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?; - let mut contents = BTreeSet::default(); - let mut sep = ""; - for &index in offset.delta.iter() { - let (content, rest) = checked_split_at(s, index).ok_or(Error)?; + let (s, offset) = s + .rsplit_once("\n") + .ok_or(Error("invalid format: should have a newline on the last line"))?; + let offset = offset + .strip_prefix(F::COMMENT_START) + .ok_or(Error("last line expected to start with a comment"))?; + let offset = offset + .strip_suffix(F::COMMENT_END) + .ok_or(Error("last line expected to end with a comment"))?; + let offset: Offset = serde_json::from_str(&offset).map_err(|_| { + Error("could not find insertion location descriptor object on last line") + })?; + let (before, mut s) = + s.split_at_checked(offset.start).ok_or(Error("invalid start: out of bounds"))?; + let mut fragments = BTreeSet::default(); + for (p, &index) in offset.fragment_lengths.iter().with_position() { + let (fragment, rest) = + s.split_at_checked(index).ok_or(Error("invalid fragment length: out of bounds"))?; s = rest; - let content = content.strip_prefix(sep).ok_or(Error)?; - contents.insert(content.to_string()); - sep = F::SEPARATOR; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + let fragment = fragment + .strip_prefix(sep) + .ok_or(Error("invalid fragment length: expected to find separator here"))?; + fragments.insert(fragment.to_string()); } Ok(SortedTemplate { format: PhantomData, before: before.to_string(), after: s.to_string(), - contents, + fragments, }) } } @@ -127,11 +134,24 @@ impl FileFormat for Js { } #[derive(Debug, Clone)] -pub(crate) struct Error; +pub(crate) struct Error(&'static str); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid template") + write!(f, "invalid template: {}", self.0) + } +} + +struct DeltaWriter { + inner: W, + delta: usize, +} + +impl fmt::Write for DeltaWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.inner.write_str(s)?; + self.delta += s.len(); + Ok(()) } } diff --git a/src/librustdoc/html/render/sorted_template/tests.rs b/src/librustdoc/html/render/sorted_template/tests.rs index 04553f65a2154..db057463005c9 100644 --- a/src/librustdoc/html/render/sorted_template/tests.rs +++ b/src/librustdoc/html/render/sorted_template/tests.rs @@ -1,6 +1,7 @@ -use super::super::sorted_template::*; use std::str::FromStr; +use super::super::sorted_template::*; + fn is_comment_js(s: &str) -> bool { s.starts_with("//") } @@ -13,7 +14,7 @@ fn is_comment_html(s: &str) -> bool { #[test] fn html_from_empty() { let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -29,7 +30,7 @@ fn html_page() { let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); for insert in inserts { template.append(insert.to_string()); } @@ -43,7 +44,7 @@ fn html_page() { #[test] fn js_from_empty() { let inserts = ["1", "2", "2", "2", "3", "1"]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -56,7 +57,7 @@ fn js_from_empty() { #[test] fn js_empty_array() { - let template = SortedTemplate::::before_after("[", "]"); + let template = SortedTemplate::::from_before_after("[", "]"); let template = format!("{template}"); let (template, end) = template.rsplit_once("\n").unwrap(); assert_eq!(template, format!("[]")); @@ -67,7 +68,7 @@ fn js_empty_array() { #[test] fn js_number_array() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -81,7 +82,7 @@ fn js_number_array() { #[test] fn magic_js_number_array() { let inserts = ["1", "1"]; - let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + let mut template = SortedTemplate::::from_template("[#]", "#").unwrap(); for insert in inserts { template.append(insert.to_string()); } @@ -95,7 +96,7 @@ fn magic_js_number_array() { #[test] fn round_trip_js() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -114,7 +115,7 @@ fn round_trip_html() { let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); template.append(inserts[0].to_string()); template.append(inserts[1].to_string()); let template = format!("{template}"); @@ -129,7 +130,7 @@ fn round_trip_html() { #[test] fn blank_js() { let inserts = ["1", "2", "3"]; - let template = SortedTemplate::::before_after("", ""); + let template = SortedTemplate::::from_before_after("", ""); let template = format!("{template}"); let (t, _) = template.rsplit_once("\n").unwrap(); assert_eq!(t, ""); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index c2d2b4cd7d9fb..ef3c35c20ab01 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,4 +1,4 @@ -//! Rustdoc writes out two kinds of shared files: +//! Rustdoc writes aut two kinds of shared files: //! - Static files, which are embedded in the rustdoc binary and are written with a //! filename that includes a hash of their contents. These will always have a new //! URL if the contents change, so they are safe to cache with the @@ -13,18 +13,16 @@ //! --resource-suffix flag and are emitted when --emit-type is empty (default) //! or contains "invocation-specific". -use std::any::Any; use std::cell::RefCell; use std::ffi::OsString; use std::fs::File; -use std::io::BufWriter; -use std::io::Write as _; +use std::io::{self, BufWriter, Write as _}; use std::iter::once; use std::marker::PhantomData; use std::path::{Component, Path, PathBuf}; use std::rc::{Rc, Weak}; use std::str::FromStr; -use std::{fmt, fs, io}; +use std::{fmt, fs}; use indexmap::IndexMap; use itertools::Itertools; @@ -35,8 +33,9 @@ use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Symbol; +use serde::de::DeserializeOwned; use serde::ser::SerializeSeq; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; @@ -48,9 +47,8 @@ use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; use crate::html::layout; -use crate::html::render::search_index::build_index; -use crate::html::render::search_index::SerializedSearchIndex; -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; +use crate::html::render::search_index::{build_index, SerializedSearchIndex}; use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::static_files::{self, suffix_path}; @@ -76,7 +74,7 @@ pub(crate) fn write_shared( let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand - let crate_name_json = SortedJson::serialize(crate_name); // "rand" + let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand" let external_crates = hack_get_external_crate_names(&cx.dst)?; let info = CrateInfo { src_files_js: SourcesPart::get(cx, &crate_name_json)?, @@ -111,11 +109,7 @@ pub(crate) fn write_shared( ); } None if opt.enable_index_page => { - write_rendered_cci::( - || CratesIndexPart::blank(cx), - dst, - &crates, - )?; + write_rendered_cci::(|| CratesIndexPart::blank(cx), dst, &crates)?; } _ => {} // they don't want an index page } @@ -171,9 +165,9 @@ fn write_search_desc( search_desc: &[(usize, String)], ) -> Result<(), Error> { let crate_name = krate.name(cx.tcx()).to_string(); - let encoded_crate_name = SortedJson::serialize(&crate_name); + let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap(); let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]); - if Path::new(&path).exists() { + if path.exists() { try_err!(fs::remove_dir_all(&path), &path); } for (i, (_, part)) in search_desc.iter().enumerate() { @@ -182,7 +176,7 @@ fn write_search_desc( &cx.shared.resource_suffix, ); let path = path.join(filename); - let part = SortedJson::serialize(&part); + let part = OrderedJson::serialize(&part).unwrap(); let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); create_parents(&path)?; try_err!(fs::write(&path, part), &path); @@ -201,20 +195,6 @@ struct CrateInfo { type_impl: PartsAndLocations, } -impl CrateInfo { - /// Gets a reference to the cross-crate information parts for `T` - fn get(&self) -> &PartsAndLocations { - (&self.src_files_js as &dyn Any) - .downcast_ref() - .or_else(|| (&self.search_index_js as &dyn Any).downcast_ref()) - .or_else(|| (&self.all_crates as &dyn Any).downcast_ref()) - .or_else(|| (&self.crates_index as &dyn Any).downcast_ref()) - .or_else(|| (&self.trait_impl as &dyn Any).downcast_ref()) - .or_else(|| (&self.type_impl as &dyn Any).downcast_ref()) - .expect("this should be an exhaustive list of `CciPart`s") - } -} - /// Paths (relative to the doc root) and their pre-merge contents #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(transparent)] @@ -263,6 +243,7 @@ impl fmt::Display for Part { trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static { /// Identifies the file format of the cross-crate information type FileFormat: sorted_template::FileFormat; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations; } #[derive(Serialize, Deserialize, Clone, Default, Debug)] @@ -270,11 +251,14 @@ struct SearchIndex; type SearchIndexPart = Part; impl CciPart for SearchIndexPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.search_index_js + } } impl SearchIndexPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var searchIndex = new Map(JSON.parse('[", r"]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; @@ -283,7 +267,7 @@ else if (window.initSearch) window.initSearch(searchIndex);", } fn get( - search_index: SortedJson, + search_index: OrderedJson, resource_suffix: &str, ) -> Result, Error> { let path = suffix_path("search-index.js", resource_suffix); @@ -294,17 +278,20 @@ else if (window.initSearch) window.initSearch(searchIndex);", #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct AllCrates; -type AllCratesPart = Part; +type AllCratesPart = Part; impl CciPart for AllCratesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.all_crates + } } impl AllCratesPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after("window.ALL_CRATES = [", "];") + SortedTemplate::from_before_after("window.ALL_CRATES = [", "];") } - fn get(crate_name_json: SortedJson) -> Result, Error> { + fn get(crate_name_json: OrderedJson) -> Result, Error> { // external hack_get_external_crate_names not needed here, because // there's no way that we write the search index but not crates.js let path = PathBuf::from("crates.js"); @@ -339,6 +326,9 @@ struct CratesIndex; type CratesIndexPart = Part; impl CciPart for CratesIndexPart { type FileFormat = sorted_template::Html; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.crates_index + } } impl CratesIndexPart { @@ -354,10 +344,11 @@ impl CratesIndexPart { }; let layout = &cx.shared.layout; let style_files = &cx.shared.style_files; - const MAGIC: &str = "\u{FFFC}"; // users are being naughty if they have this - let content = format!("

    List of all crates

      {MAGIC}
    "); + const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this + let content = + format!("

    List of all crates

      {DELIMITER}
    "); let template = layout::render(layout, &page, "", content, &style_files); - match SortedTemplate::magic(&template, MAGIC) { + match SortedTemplate::from_template(&template, DELIMITER) { Ok(template) => template, Err(e) => panic!( "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}" @@ -385,6 +376,9 @@ struct Sources; type SourcesPart = Part; impl CciPart for SourcesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.src_files_js + } } impl SourcesPart { @@ -392,14 +386,14 @@ impl SourcesPart { // This needs to be `var`, not `const`. // This variable needs declared in the current global scope so that if // src-script.js loads first, it can pick it up. - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var srcIndex = new Map(JSON.parse('[", r"]')); createSrcSidebar();", ) } - fn get(cx: &Context<'_>, crate_name: &SortedJson) -> Result, Error> { + fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result, Error> { let hierarchy = Rc::new(Hierarchy::default()); cx.shared .local_sources @@ -408,7 +402,7 @@ createSrcSidebar();", .for_each(|source| hierarchy.add_path(source)); let path = suffix_path("src-files.js", &cx.shared.resource_suffix); let hierarchy = hierarchy.to_json_string(); - let part = SortedJson::array_unsorted([crate_name, &hierarchy]); + let part = OrderedJson::array_unsorted([crate_name, &hierarchy]); let part = EscapedJson::from(part); Ok(PartsAndLocations::with(path, part)) } @@ -428,21 +422,23 @@ impl Hierarchy { Self { elem, parent: Rc::downgrade(parent), ..Self::default() } } - fn to_json_string(&self) -> SortedJson { + fn to_json_string(&self) -> OrderedJson { let subs = self.children.borrow(); let files = self.elems.borrow(); - let name = SortedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")); + let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")) + .unwrap(); let mut out = Vec::from([name]); if !subs.is_empty() || !files.is_empty() { let subs = subs.iter().map(|(_, s)| s.to_json_string()); - out.push(SortedJson::array(subs)); + out.push(OrderedJson::array_sorted(subs)); } if !files.is_empty() { - let files = - files.iter().map(|s| SortedJson::serialize(s.to_str().expect("invalid osstring"))); - out.push(SortedJson::array(files)); + let files = files + .iter() + .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap()); + out.push(OrderedJson::array_sorted(files)); } - SortedJson::array_unsorted(out) + OrderedJson::array_unsorted(out) } fn add_path(self: &Rc, path: &Path) { @@ -481,14 +477,17 @@ impl Hierarchy { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TypeAlias; -type TypeAliasPart = Part; +type TypeAliasPart = Part; impl CciPart for TypeAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.type_impl + } } impl TypeAliasPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var type_impls = Object.fromEntries([", r"]); @@ -504,7 +503,7 @@ impl TypeAliasPart { fn get( cx: &mut Context<'_>, krate: &Crate, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &Rc::clone(&cx.shared).cache; let mut path_parts = PartsAndLocations::default(); @@ -594,9 +593,10 @@ impl TypeAliasPart { aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] )); - let part = - SortedJson::array(impls.iter().map(SortedJson::serialize).collect::>()); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + let part = OrderedJson::array_sorted( + impls.iter().map(OrderedJson::serialize).collect::, _>>().unwrap(), + ); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -604,14 +604,17 @@ impl TypeAliasPart { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TraitAlias; -type TraitAliasPart = Part; +type TraitAliasPart = Part; impl CciPart for TraitAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.trait_impl + } } impl TraitAliasPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var implementors = Object.fromEntries([", r"]); @@ -626,7 +629,7 @@ impl TraitAliasPart { fn get( cx: &mut Context<'_>, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &cx.shared.cache; let mut path_parts = PartsAndLocations::default(); @@ -688,10 +691,14 @@ impl TraitAliasPart { } path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - let part = SortedJson::array( - implementors.iter().map(SortedJson::serialize).collect::>(), + let part = OrderedJson::array_sorted( + implementors + .iter() + .map(OrderedJson::serialize) + .collect::, _>>() + .unwrap(), ); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -864,13 +871,15 @@ fn get_path_parts( crates_info: &[CrateInfo], ) -> FxHashMap> { let mut templates: FxHashMap> = FxHashMap::default(); - crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each( - |(path, part)| { + crates_info + .iter() + .map(|crate_info| T::from_crate_info(crate_info).parts.iter()) + .flatten() + .for_each(|(path, part)| { let path = dst.join(&path); let part = part.to_string(); templates.entry(path).or_default().push(part); - }, - ); + }); templates } diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs index 000e233aec00f..4d1874b7df5f9 100644 --- a/src/librustdoc/html/render/write_shared/tests.rs +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -1,4 +1,4 @@ -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::sorted_template::{Html, SortedTemplate}; use crate::html::render::write_shared::*; @@ -26,13 +26,13 @@ fn sources_template() { r"var srcIndex = new Map(JSON.parse('[]')); createSrcSidebar();" ); - template.append(EscapedJson::from(SortedJson::serialize("u")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("u").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u"]')); createSrcSidebar();"# ); - template.append(EscapedJson::from(SortedJson::serialize("v")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("v").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u","v"]')); @@ -42,7 +42,8 @@ createSrcSidebar();"# #[test] fn sources_parts() { - let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap(); + let parts = + SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap(); assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js")); assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#); } @@ -51,15 +52,15 @@ fn sources_parts() { fn all_crates_template() { let mut template = AllCratesPart::blank(); assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];"); - template.append(EscapedJson::from(SortedJson::serialize("b")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("b").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#); - template.append(EscapedJson::from(SortedJson::serialize("a")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("a").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#); } #[test] fn all_crates_parts() { - let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap(); + let parts = AllCratesPart::get(OrderedJson::serialize("crate").unwrap()).unwrap(); assert_eq!(&parts.parts[0].0, Path::new("crates.js")); assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#); } @@ -73,14 +74,14 @@ fn search_index_template() { if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2]]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]')); @@ -119,7 +120,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -131,7 +132,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -159,7 +160,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -171,7 +172,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -189,7 +190,7 @@ fn type_alias_template() { fn read_template_test() { let path = tempfile::TempDir::new().unwrap(); let path = path.path().join("file.html"); - let make_blank = || SortedTemplate::::before_after("
    ", "
    "); + let make_blank = || SortedTemplate::::from_before_after("
    ", "
    "); let template = read_template_or_blank(make_blank, &path).unwrap(); assert_eq!(but_last_line(&template.to_string()), "
    "); From 0732f7d5e1ea2b735db98d53b628e512f8d5a372 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 8 Oct 2023 10:54:15 +0200 Subject: [PATCH 069/685] Stabilize `Ready::into_inner()` --- library/core/src/future/ready.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/core/src/future/ready.rs b/library/core/src/future/ready.rs index a07b63fb62b90..6f6da8ce51ddf 100644 --- a/library/core/src/future/ready.rs +++ b/library/core/src/future/ready.rs @@ -34,13 +34,12 @@ impl Ready { /// # Examples /// /// ``` - /// #![feature(ready_into_inner)] /// use std::future; /// /// let a = future::ready(1); /// assert_eq!(a.into_inner(), 1); /// ``` - #[unstable(feature = "ready_into_inner", issue = "101196")] + #[stable(feature = "ready_into_inner", since = "CURRENT_RUSTC_VERSION")] #[must_use] #[inline] pub fn into_inner(self) -> T { From 6ce7e6d99f8bc97375f70331204c78f6438e7495 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Aug 2024 11:56:12 -0400 Subject: [PATCH 070/685] Rename struct_tail_erasing_lifetimes to struct_tail_for_codegen --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index f206b2ceebcb1..553af913ef9df 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -142,7 +142,7 @@ fn check_rvalue<'tcx>( // We cannot allow this for now. return Err((span, "unsizing casts are only allowed for references right now".into())); }; - let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); + let unsized_ty = tcx.struct_tail_for_codegen(pointee_ty, tcx.param_env(def_id)); if let ty::Slice(_) | ty::Str = unsized_ty.kind() { check_operand(tcx, op, span, body, msrv)?; // Casting/coercing things to slices is fine. From b4f057f01ddb414d250c0a70d1feb966cdfa9d99 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Fri, 26 Jul 2024 17:09:32 +0000 Subject: [PATCH 071/685] fix typos, more Self typos in comments, remove references to crate-info, Self type in ordered_json and sorted_template --- src/librustdoc/html/render/ordered_json.rs | 20 ++++++++----------- src/librustdoc/html/render/sorted_template.rs | 6 +++--- src/librustdoc/html/render/write_shared.rs | 4 ++-- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/librustdoc/html/render/ordered_json.rs b/src/librustdoc/html/render/ordered_json.rs index 3f76ff659d04e..7abe40eef3bdb 100644 --- a/src/librustdoc/html/render/ordered_json.rs +++ b/src/librustdoc/html/render/ordered_json.rs @@ -5,7 +5,7 @@ use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use serde_json::Value; -/// Prerenedered json. +/// Prerendered json. /// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] @@ -16,25 +16,21 @@ pub(crate) struct OrderedJson(String); impl OrderedJson { /// If you pass in an array, it will not be sorted. pub(crate) fn serialize(item: T) -> Result { - Ok(OrderedJson(serde_json::to_string(&item)?)) + Ok(Self(serde_json::to_string(&item)?)) } /// Serializes and sorts - pub(crate) fn array_sorted, I: IntoIterator>( - items: I, - ) -> Self { + pub(crate) fn array_sorted, I: IntoIterator>(items: I) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); - OrderedJson(format!("[{}]", items)) + Self(format!("[{}]", items)) } - pub(crate) fn array_unsorted, I: IntoIterator>( - items: I, - ) -> Self { + pub(crate) fn array_unsorted, I: IntoIterator>(items: I) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); - OrderedJson(format!("[{items}]")) + Self(format!("[{items}]")) } } @@ -48,7 +44,7 @@ impl From for OrderedJson { fn from(value: Value) -> Self { let serialized = serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); - OrderedJson(serialized) + Self(serialized) } } @@ -69,7 +65,7 @@ pub(crate) struct EscapedJson(OrderedJson); impl From for EscapedJson { fn from(json: OrderedJson) -> Self { - EscapedJson(json) + Self(json) } } diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 1dc70408f013d..28f7766d7c7ac 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -28,7 +28,7 @@ struct Offset { impl SortedTemplate { /// Generate this template from arbitary text. - /// Will insert wherever the substring `magic` can be found. + /// Will insert wherever the substring `delimiter` can be found. /// Errors if it does not appear exactly once. pub(crate) fn from_template(template: &str, delimiter: &str) -> Result { let mut split = template.split(delimiter); @@ -45,7 +45,7 @@ impl SortedTemplate { pub(crate) fn from_before_after(before: S, after: T) -> Self { let before = before.to_string(); let after = after.to_string(); - SortedTemplate { format: PhantomData, before, after, fragments: Default::default() } + Self { format: PhantomData, before, after, fragments: Default::default() } } } @@ -100,7 +100,7 @@ impl FromStr for SortedTemplate { .ok_or(Error("invalid fragment length: expected to find separator here"))?; fragments.insert(fragment.to_string()); } - Ok(SortedTemplate { + Ok(Self { format: PhantomData, before: before.to_string(), after: s.to_string(), diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index ef3c35c20ab01..a18b7a252a402 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -55,7 +55,7 @@ use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; use crate::{try_err, try_none}; -/// Write crate-info.json cross-crate information, static files, invocation-specific files, etc. to disk +/// Write cross-crate information files, static files, invocation-specific files, etc. to disk pub(crate) fn write_shared( cx: &mut Context<'_>, krate: &Crate, @@ -184,7 +184,7 @@ fn write_search_desc( Ok(()) } -/// Written to `crate-info.json`. Contains pre-rendered contents to insert into the CCI template +/// Contains pre-rendered contents to insert into the CCI template #[derive(Serialize, Deserialize, Clone, Debug)] struct CrateInfo { src_files_js: PartsAndLocations, From 1ac76a2062b94b72c36650d21ac1af40b4aea0e3 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 8 Aug 2024 19:13:50 +0200 Subject: [PATCH 072/685] Merge commit 'cb806113e0f83a8f9b47d35b453b676543bcc40e' into clippy-subtree-update --- .cargo/config.toml | 7 + .github/workflows/clippy.yml | 13 +- .github/workflows/clippy_bors.yml | 17 +- .github/workflows/clippy_dev.yml | 5 +- .github/workflows/lintcheck.yml | 4 +- CHANGELOG.md | 2 + Cargo.toml | 3 +- book/src/lint_configuration.md | 6 +- clippy_config/Cargo.toml | 4 +- clippy_config/src/conf.rs | 724 +++++++++--------- clippy_config/src/lib.rs | 3 +- clippy_config/src/metadata.rs | 105 +-- clippy_dev/Cargo.toml | 3 - clippy_dev/src/fmt.rs | 392 +++++++--- clippy_dev/src/lib.rs | 1 - clippy_dev/src/main.rs | 5 +- clippy_dev/src/serve.rs | 15 +- clippy_dev/src/update_lints.rs | 324 +++----- clippy_lints/Cargo.toml | 1 - clippy_lints/src/approx_const.rs | 2 +- clippy_lints/src/as_conversions.rs | 10 +- clippy_lints/src/asm_syntax.rs | 22 +- clippy_lints/src/assertions_on_constants.rs | 4 +- .../src/assertions_on_result_states.rs | 47 +- clippy_lints/src/assigning_clones.rs | 29 +- clippy_lints/src/attrs/allow_attributes.rs | 20 +- .../attrs/allow_attributes_without_reason.rs | 10 +- clippy_lints/src/bool_to_int_with_if.rs | 4 +- clippy_lints/src/booleans.rs | 10 +- clippy_lints/src/casts/cast_lossless.rs | 8 +- clippy_lints/src/casts/cast_nan_to_int.rs | 4 +- .../src/casts/cast_possible_truncation.rs | 4 +- clippy_lints/src/casts/cast_sign_loss.rs | 6 +- .../src/casts/fn_to_numeric_cast_any.rs | 37 +- clippy_lints/src/casts/zero_ptr.rs | 4 +- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/comparison_chain.rs | 4 +- clippy_lints/src/create_dir.rs | 24 +- clippy_lints/src/dbg_macro.rs | 108 +-- clippy_lints/src/declared_lints.rs | 3 +- clippy_lints/src/default.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 23 +- .../src/default_union_representation.rs | 16 +- clippy_lints/src/deprecated_lints.rs | 394 ++++------ clippy_lints/src/dereference.rs | 4 +- clippy_lints/src/doc/mod.rs | 9 +- clippy_lints/src/drop_forget_ref.rs | 18 +- clippy_lints/src/else_if_without_else.rs | 10 +- clippy_lints/src/empty_drop.rs | 19 +- clippy_lints/src/endian_bytes.rs | 89 +-- clippy_lints/src/enum_clike.rs | 2 +- clippy_lints/src/eta_reduction.rs | 327 ++++---- clippy_lints/src/exhaustive_items.rs | 6 +- .../src/field_scoped_visibility_modifiers.rs | 10 +- clippy_lints/src/float_literal.rs | 45 +- clippy_lints/src/floating_point_arithmetic.rs | 36 +- clippy_lints/src/format_push_string.rs | 10 +- clippy_lints/src/from_str_radix_10.rs | 6 +- clippy_lints/src/if_let_mutex.rs | 66 +- clippy_lints/src/if_not_else.rs | 4 +- clippy_lints/src/if_then_some_else_none.rs | 47 +- clippy_lints/src/implicit_hasher.rs | 53 +- clippy_lints/src/implicit_return.rs | 21 +- clippy_lints/src/implicit_saturating_add.rs | 8 +- clippy_lints/src/incompatible_msrv.rs | 7 +- clippy_lints/src/index_refutable_slice.rs | 4 +- clippy_lints/src/indexing_slicing.rs | 11 +- clippy_lints/src/infinite_iter.rs | 1 - clippy_lints/src/inherent_impl.rs | 9 +- .../src/invalid_upcast_comparisons.rs | 4 +- clippy_lints/src/large_include_file.rs | 16 +- clippy_lints/src/let_underscore.rs | 58 +- clippy_lints/src/lib.deprecated.rs | 78 -- clippy_lints/src/lib.rs | 81 +- clippy_lints/src/literal_representation.rs | 77 +- .../src/loops/explicit_counter_loop.rs | 8 +- clippy_lints/src/loops/for_kv_map.rs | 7 +- .../src/loops/manual_while_let_some.rs | 7 +- clippy_lints/src/loops/mod.rs | 10 +- clippy_lints/src/loops/needless_range_loop.rs | 11 +- .../src/loops/unused_enumerate_index.rs | 7 +- .../src/loops/while_immutable_condition.rs | 4 +- .../src/loops/while_let_on_iterator.rs | 7 +- clippy_lints/src/manual_clamp.rs | 13 +- clippy_lints/src/manual_float_methods.rs | 7 +- clippy_lints/src/manual_is_ascii_check.rs | 4 +- clippy_lints/src/manual_rem_euclid.rs | 8 +- clippy_lints/src/manual_rotate.rs | 4 +- .../src/manual_slice_size_calculation.rs | 4 +- clippy_lints/src/manual_strip.rs | 26 +- clippy_lints/src/manual_unwrap_or_default.rs | 4 +- clippy_lints/src/matches/manual_unwrap_or.rs | 4 +- clippy_lints/src/matches/match_ref_pats.rs | 8 +- clippy_lints/src/matches/match_wild_enum.rs | 39 +- .../src/matches/match_wild_err_arm.rs | 4 +- clippy_lints/src/matches/mod.rs | 6 +- clippy_lints/src/matches/overlapping_arms.rs | 30 +- clippy_lints/src/matches/redundant_guards.rs | 4 +- .../matches/rest_pat_in_fully_bound_struct.rs | 10 +- clippy_lints/src/matches/single_match.rs | 394 ++++++---- clippy_lints/src/matches/try_err.rs | 39 +- .../src/methods/bind_instead_of_map.rs | 19 +- clippy_lints/src/methods/clone_on_ref_ptr.rs | 23 +- clippy_lints/src/methods/filetype_is_file.rs | 9 +- clippy_lints/src/methods/get_unwrap.rs | 44 +- clippy_lints/src/methods/implicit_clone.rs | 6 +- .../src/methods/is_digit_ascii_radix.rs | 4 +- clippy_lints/src/methods/is_empty.rs | 4 +- clippy_lints/src/methods/iter_kv_map.rs | 15 +- clippy_lints/src/methods/iter_nth_zero.rs | 4 +- clippy_lints/src/methods/iter_skip_zero.rs | 4 +- .../src/methods/iterator_step_by_zero.rs | 4 +- clippy_lints/src/methods/map_clone.rs | 6 +- clippy_lints/src/methods/map_err_ignore.rs | 12 +- clippy_lints/src/methods/mod.rs | 28 +- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/methods/repeat_once.rs | 4 +- clippy_lints/src/methods/str_splitn.rs | 4 +- .../src/methods/unnecessary_min_or_max.rs | 13 +- .../src/methods/unnecessary_to_owned.rs | 13 +- .../src/methods/unused_enumerate_index.rs | 7 +- clippy_lints/src/methods/useless_asref.rs | 6 +- .../src/methods/verbose_file_reads.rs | 7 +- .../src/methods/wrong_self_convention.rs | 2 +- clippy_lints/src/minmax.rs | 20 +- clippy_lints/src/misc_early/literal_suffix.rs | 30 +- .../src/misc_early/unneeded_field_pattern.rs | 21 +- clippy_lints/src/missing_assert_message.rs | 10 +- clippy_lints/src/missing_const_for_fn.rs | 2 +- clippy_lints/src/missing_trait_methods.rs | 46 +- .../src/mixed_read_write_in_expression.rs | 12 +- clippy_lints/src/module_style.rs | 32 +- .../src/needless_borrows_for_generic_args.rs | 48 +- clippy_lints/src/needless_pass_by_value.rs | 11 +- clippy_lints/src/no_effect.rs | 2 +- clippy_lints/src/non_copy_const.rs | 4 +- clippy_lints/src/only_used_in_recursion.rs | 1 - .../operators/absurd_extreme_comparisons.rs | 4 +- .../src/operators/arithmetic_side_effects.rs | 10 +- clippy_lints/src/operators/bit_mask.rs | 4 +- .../src/operators/const_comparisons.rs | 7 +- clippy_lints/src/operators/duration_subsec.rs | 4 +- clippy_lints/src/operators/erasing_op.rs | 6 +- clippy_lints/src/operators/float_cmp.rs | 8 +- clippy_lints/src/operators/identity_op.rs | 13 +- .../src/operators/integer_division.rs | 14 +- .../src/operators/modulo_arithmetic.rs | 16 +- .../src/operators/numeric_arithmetic.rs | 4 +- clippy_lints/src/operators/op_ref.rs | 6 +- clippy_lints/src/option_if_let_else.rs | 6 +- clippy_lints/src/partial_pub_fields.rs | 26 +- clippy_lints/src/pathbuf_init_then_push.rs | 4 +- clippy_lints/src/pattern_type_mismatch.rs | 30 +- clippy_lints/src/pub_use.rs | 14 +- clippy_lints/src/question_mark.rs | 8 +- clippy_lints/src/question_mark_used.rs | 10 +- clippy_lints/src/ranges.rs | 15 +- clippy_lints/src/redundant_clone.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 68 +- clippy_lints/src/ref_patterns.rs | 14 +- clippy_lints/src/regex.rs | 4 +- clippy_lints/src/renamed_lints.rs | 65 -- clippy_lints/src/repeat_vec_with_capacity.rs | 4 +- clippy_lints/src/returns.rs | 2 +- clippy_lints/src/semicolon_block.rs | 12 +- clippy_lints/src/set_contains_or_insert.rs | 33 +- clippy_lints/src/shadow.rs | 13 +- .../src/single_char_lifetime_names.rs | 10 +- clippy_lints/src/size_of_ref.rs | 5 +- clippy_lints/src/std_instead_of_core.rs | 69 +- clippy_lints/src/strings.rs | 23 +- .../src/suspicious_xor_used_as_pow.rs | 15 +- clippy_lints/src/swap.rs | 4 +- clippy_lints/src/tests_outside_test_module.rs | 10 +- clippy_lints/src/transmute/mod.rs | 4 +- .../src/transmute/transmute_null_to_fn.rs | 7 +- .../src/transmute/transmuting_null.rs | 4 +- clippy_lints/src/types/borrowed_box.rs | 24 +- clippy_lints/src/types/rc_buffer.rs | 62 +- clippy_lints/src/types/rc_mutex.rs | 14 +- .../src/undocumented_unsafe_blocks.rs | 53 +- clippy_lints/src/unicode.rs | 64 +- clippy_lints/src/unused_result_ok.rs | 59 ++ .../interning_defined_symbol.rs | 6 +- .../src/utils/internal_lints/invalid_paths.rs | 8 +- .../internal_lints/lint_without_lint_pass.rs | 85 +- .../internal_lints/metadata_collector.rs | 117 ++- clippy_lints/src/vec.rs | 4 +- clippy_lints/src/visibility.rs | 43 +- clippy_lints/src/zero_div_zero.rs | 7 +- clippy_utils/Cargo.toml | 3 - clippy_utils/src/consts.rs | 279 ++++--- clippy_utils/src/diagnostics.rs | 29 - clippy_utils/src/eager_or_lazy.rs | 16 +- clippy_utils/src/higher.rs | 13 +- clippy_utils/src/hir_utils.rs | 12 +- clippy_utils/src/lib.rs | 46 +- clippy_utils/src/paths.rs | 1 - clippy_utils/src/source.rs | 129 ++-- clippy_utils/src/ty.rs | 13 - clippy_utils/src/visitors.rs | 98 ++- declare_clippy_lint/Cargo.toml | 3 - declare_clippy_lint/src/lib.rs | 1 - lintcheck/Cargo.toml | 3 - lintcheck/src/config.rs | 8 +- lintcheck/src/input.rs | 6 +- lintcheck/src/main.rs | 28 +- rust-toolchain | 2 +- rustc_tools_util/Cargo.toml | 3 - rustc_tools_util/src/lib.rs | 59 +- src/driver.rs | 2 - src/main.rs | 1 - tests/check-fmt.rs | 1 - tests/compile-test.rs | 6 +- tests/dogfood.rs | 1 - tests/integration.rs | 1 - tests/lint_message_convention.rs | 1 - tests/missing-test-files.rs | 1 - .../ui-internal/default_deprecation_reason.rs | 30 - .../default_deprecation_reason.stderr | 22 - .../excessive_nesting/excessive_nesting.rs | 20 +- .../excessive_nesting.stderr | 74 +- tests/ui-toml/unwrap_used/unwrap_used.stderr | 134 +++- tests/ui/assigning_clones.fixed | 68 ++ tests/ui/assigning_clones.rs | 68 ++ tests/ui/assigning_clones.stderr | 86 ++- tests/ui/bind_instead_of_map_multipart.stderr | 10 +- tests/ui/crashes/ice-3717.stderr | 11 +- tests/ui/crashes/ice-6254.rs | 3 +- tests/ui/create_dir.stderr | 13 +- tests/ui/dbg_macro/dbg_macro.stderr | 4 +- tests/ui/dbg_macro/dbg_macro_unfixable.stderr | 2 +- tests/ui/deprecated.rs | 31 +- tests/ui/deprecated.stderr | 68 +- tests/ui/deprecated_old.rs | 9 - tests/ui/deprecated_old.stderr | 23 - tests/ui/deref_by_slicing.fixed | 4 + tests/ui/deref_by_slicing.rs | 4 + tests/ui/deref_by_slicing.stderr | 8 +- tests/ui/doc/footnote_issue_13183.rs | 10 + tests/ui/empty_drop.stderr | 7 +- tests/ui/eta.fixed | 17 + tests/ui/eta.rs | 17 + tests/ui/eta.stderr | 74 +- tests/ui/excessive_precision.stderr | 111 ++- tests/ui/explicit_counter_loop.rs | 13 + tests/ui/explicit_counter_loop.stderr | 8 +- tests/ui/fn_to_numeric_cast_any.stderr | 118 ++- tests/ui/for_kv_map.fixed | 10 + tests/ui/for_kv_map.rs | 10 + tests/ui/for_kv_map.stderr | 13 +- tests/ui/get_unwrap.stderr | 152 +++- tests/ui/if_let_mutex.rs | 9 + tests/ui/if_let_mutex.stderr | 24 +- tests/ui/if_then_some_else_none.fixed | 119 +++ tests/ui/if_then_some_else_none.rs | 2 +- tests/ui/if_then_some_else_none.stderr | 19 +- tests/ui/implicit_hasher.fixed | 102 +++ tests/ui/implicit_hasher.rs | 9 +- tests/ui/implicit_hasher.stderr | 131 +++- tests/ui/implicit_return.stderr | 104 ++- tests/ui/lossy_float_literal.stderr | 76 +- tests/ui/missing_trait_methods.rs | 8 + tests/ui/missing_trait_methods.stderr | 38 +- .../needless_borrows_for_generic_args.fixed | 8 + tests/ui/needless_borrows_for_generic_args.rs | 8 + tests/ui/needless_pub_self.stderr | 11 +- tests/ui/nonminimal_bool.rs | 6 + tests/ui/patterns.fixed | 2 +- tests/ui/patterns.rs | 2 +- tests/ui/rename.fixed | 131 ++-- tests/ui/rename.rs | 131 ++-- tests/ui/rename.stderr | 158 ++-- tests/ui/set_contains_or_insert.rs | 83 +- tests/ui/set_contains_or_insert.stderr | 72 +- tests/ui/single_match.fixed | 43 ++ tests/ui/single_match.rs | 49 ++ tests/ui/single_match.stderr | 20 +- tests/ui/std_instead_of_core.fixed | 17 +- tests/ui/std_instead_of_core.rs | 17 +- tests/ui/std_instead_of_core.stderr | 14 +- tests/ui/suspicious_xor_used_as_pow.stderr | 47 +- tests/ui/try_err.fixed | 10 +- tests/ui/try_err.rs | 10 +- tests/ui/uninit_vec.rs | 2 +- tests/ui/unneeded_field_pattern.rs | 2 +- tests/ui/unused_result_ok.fixed | 40 + tests/ui/unused_result_ok.rs | 40 + tests/ui/unused_result_ok.stderr | 52 ++ tests/ui/unwrap_expect_used.rs | 2 +- tests/ui/while_let_on_iterator.fixed | 9 + tests/ui/while_let_on_iterator.rs | 9 + tests/ui/while_let_on_iterator.stderr | 8 +- tests/versioncheck.rs | 1 - tests/workspace_test/path_dep/Cargo.toml | 3 + util/gh-pages/index.html | 4 +- util/gh-pages/script.js | 74 +- 297 files changed, 5572 insertions(+), 4012 deletions(-) delete mode 100644 clippy_lints/src/lib.deprecated.rs delete mode 100644 clippy_lints/src/renamed_lints.rs create mode 100644 clippy_lints/src/unused_result_ok.rs delete mode 100644 tests/ui-internal/default_deprecation_reason.rs delete mode 100644 tests/ui-internal/default_deprecation_reason.stderr delete mode 100644 tests/ui/deprecated_old.rs delete mode 100644 tests/ui/deprecated_old.stderr create mode 100644 tests/ui/doc/footnote_issue_13183.rs create mode 100644 tests/ui/if_then_some_else_none.fixed create mode 100644 tests/ui/implicit_hasher.fixed create mode 100644 tests/ui/unused_result_ok.fixed create mode 100644 tests/ui/unused_result_ok.rs create mode 100644 tests/ui/unused_result_ok.stderr diff --git a/.cargo/config.toml b/.cargo/config.toml index 7afdd068a9905..ce07290d1e1e5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -13,6 +13,13 @@ target-dir = "target" [unstable] binary-dep-depinfo = true +profile-rustflags = true [profile.dev] split-debuginfo = "unpacked" + +# Add back the containing directory of the packages we have to refer to using --manifest-path +[profile.dev.package.clippy_dev] +rustflags = ["--remap-path-prefix", "=clippy_dev"] +[profile.dev.package.lintcheck] +rustflags = ["--remap-path-prefix", "=lintcheck"] diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 06bf3b6fdbfab..0a0538490cce9 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -25,6 +25,7 @@ env: CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings concurrency: # For a given workflow, if we push to the same PR, cancel all previous builds on that PR. @@ -47,25 +48,25 @@ jobs: # Run - name: Build - run: cargo build --tests --features deny-warnings,internal + run: cargo build --tests --features internal - name: Test - run: cargo test --features deny-warnings,internal + run: cargo test --features internal - name: Test clippy_lints - run: cargo test --features deny-warnings,internal + run: cargo test --features internal working-directory: clippy_lints - name: Test clippy_utils - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_utils - name: Test rustc_tools_util - run: cargo test --features deny-warnings + run: cargo test working-directory: rustc_tools_util - name: Test clippy_dev - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_dev - name: Test clippy-driver diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 1f4bec9291823..10e18e84c89fd 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -11,6 +11,7 @@ env: CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings concurrency: # For a given workflow, if we push to the same branch, cancel all previous builds on that branch. @@ -85,34 +86,34 @@ jobs: # Run - name: Build - run: cargo build --tests --features deny-warnings,internal + run: cargo build --tests --features internal - name: Test if: matrix.host == 'x86_64-unknown-linux-gnu' - run: cargo test --features deny-warnings,internal + run: cargo test --features internal - name: Test if: matrix.host != 'x86_64-unknown-linux-gnu' - run: cargo test --features deny-warnings,internal -- --skip dogfood + run: cargo test --features internal -- --skip dogfood - name: Test clippy_lints - run: cargo test --features deny-warnings,internal + run: cargo test --features internal working-directory: clippy_lints - name: Test clippy_utils - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_utils - name: Test clippy_config - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_config - name: Test rustc_tools_util - run: cargo test --features deny-warnings + run: cargo test working-directory: rustc_tools_util - name: Test clippy_dev - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_dev - name: Test clippy-driver diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index 37f18a4c08740..cf0a8bde202ad 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -16,6 +16,7 @@ on: env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings jobs: clippy_dev: @@ -28,7 +29,7 @@ jobs: # Run - name: Build - run: cargo build --features deny-warnings + run: cargo build working-directory: clippy_dev - name: Test update_lints @@ -38,6 +39,8 @@ jobs: run: cargo dev fmt --check - name: Test cargo dev new lint + env: + RUSTFLAGS: -A unused-imports run: | cargo dev new_lint --name new_early_pass --pass early cargo dev new_lint --name new_late_pass --pass late diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 6a5139b6dc0bb..3cbda0b382436 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -58,7 +58,7 @@ jobs: - name: Run lintcheck if: steps.cache-json.outputs.cache-hit != 'true' - run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml + run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload base JSON uses: actions/upload-artifact@v4 @@ -86,7 +86,7 @@ jobs: run: cargo build --manifest-path=lintcheck/Cargo.toml - name: Run lintcheck - run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml + run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload head JSON uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c03b03d9be3..fddc2fd994e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5830,6 +5830,7 @@ Released 2018-09-13 [`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use +[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push @@ -5998,6 +5999,7 @@ Released 2018-09-13 [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label [`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable +[`unused_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_result_ok [`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit diff --git a/Cargo.toml b/Cargo.toml index bb4dc97e748e3..78409c7a09e29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ color-print = "0.3.4" anstream = "0.6.0" [dev-dependencies] -ui_test = "0.24" +ui_test = "0.25" regex = "1.5.5" toml = "0.7.3" walkdir = "2.3" @@ -51,7 +51,6 @@ tokio = { version = "1", features = ["io-util"] } rustc_tools_util = "0.3.0" [features] -deny-warnings = ["clippy_lints/deny-warnings"] integration = ["tempfile"] internal = ["clippy_lints/internal", "tempfile"] diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index fb717a2c166d4..e3d550b146629 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -364,7 +364,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat ## `await-holding-invalid-types` - +The list of types which may not be held across an await point. **Default Value:** `[]` @@ -668,6 +668,8 @@ crate. For example, `pub(crate)` items. ## `msrv` The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` +**Default Value:** `current version` + --- **Affected lints:** * [`allow_attributes`](https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes) @@ -862,6 +864,8 @@ The maximum number of lines a function or method can have The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. By default there is no limit +**Default Value:** `target_pointer_width * 2` + --- **Affected lints:** * [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index e1b2edc8a6ff9..d5b28e2532371 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "0.12" rustc-semver = "1.1" serde = { version = "1.0", features = ["derive"] } toml = "0.7.3" @@ -13,9 +14,6 @@ toml = "0.7.3" [dev-dependencies] walkdir = "2.3" -[features] -deny-warnings = [] - [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] rustc_private = true diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 63140a36875da..4c2a8255d6b65 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -123,7 +123,8 @@ macro_rules! define_Conf { $(#[doc = $doc:literal])+ $(#[conf_deprecated($dep:literal, $new_conf:ident)])? $(#[default_text = $default_text:expr])? - ($name:ident: $ty:ty = $default:expr), + $(#[lints($($for_lints:ident),* $(,)?)])? + $name:ident: $ty:ty = $default:expr, )*) => { /// Clippy lint configuration pub struct Conf { @@ -201,29 +202,128 @@ macro_rules! define_Conf { } pub fn get_configuration_metadata() -> Vec { - let mut sorted = vec![ - $( - { - let deprecation_reason = wrap_option!($($dep)?); - - ClippyConfiguration::new( - stringify!($name), - default_text!(defaults::$name() $(, $default_text)?), - concat!($($doc, '\n',)*), - deprecation_reason, - ) - }, - )+ - ]; - sorted.sort_by(|a, b| a.name.cmp(&b.name)); - sorted + vec![$( + ClippyConfiguration { + name: stringify!($name).replace('_', "-"), + default: default_text!(defaults::$name() $(, $default_text)?), + lints: &[$($(stringify!($for_lints)),*)?], + doc: concat!($($doc, '\n',)*), + deprecation_reason: wrap_option!($($dep)?) + }, + )*] } }; } define_Conf! { - /// Lint: ARITHMETIC_SIDE_EFFECTS. + /// Which crates to allow absolute paths from + #[lints(absolute_paths)] + absolute_paths_allowed_crates: FxHashSet = FxHashSet::default(), + /// The maximum number of segments a path can have before being linted, anything above this will + /// be linted. + #[lints(absolute_paths)] + absolute_paths_max_segments: u64 = 2, + /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block + #[lints(undocumented_unsafe_blocks)] + accept_comment_above_attributes: bool = true, + /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block + #[lints(undocumented_unsafe_blocks)] + accept_comment_above_statement: bool = true, + /// Don't lint when comparing the result of a modulo operation to zero. + #[lints(modulo_arithmetic)] + allow_comparison_to_zero: bool = true, + /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` + #[lints(dbg_macro)] + allow_dbg_in_tests: bool = false, + /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` + #[lints(expect_used)] + allow_expect_in_tests: bool = false, + /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` + #[lints(uninlined_format_args)] + allow_mixed_uninlined_format_args: bool = true, + /// Whether to allow `r#""#` when `r""` can be used + #[lints(unnecessary_raw_string_hashes)] + allow_one_hash_in_raw_strings: bool = false, + /// Whether `panic` should be allowed in test functions or `#[cfg(test)]` + #[lints(panic)] + allow_panic_in_tests: bool = false, + /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` + #[lints(print_stderr, print_stdout)] + allow_print_in_tests: bool = false, + /// Whether to allow module inception if it's not public. + #[lints(module_inception)] + allow_private_module_inception: bool = false, + /// List of trait paths to ignore when checking renamed function parameters. + /// + /// #### Example + /// + /// ```toml + /// allow-renamed-params-for = [ "std::convert::From" ] + /// ``` + /// + /// #### Noteworthy + /// + /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` + /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the + /// default configuration of Clippy. By default, any configuration will replace the default value. + #[lints(renamed_function_params)] + allow_renamed_params_for: Vec = + DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(), + /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` + #[lints(unwrap_used)] + allow_unwrap_in_tests: bool = false, + /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]` + #[lints(useless_vec)] + allow_useless_vec_in_tests: bool = false, + /// Additional dotfiles (files or directories starting with a dot) to allow + #[lints(path_ends_with_ext)] + allowed_dotfiles: Vec = Vec::default(), + /// A list of crate names to allow duplicates of + #[lints(multiple_crate_versions)] + allowed_duplicate_crates: FxHashSet = FxHashSet::default(), + /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of + /// the list to indicate, that the configured values should be appended to the default + /// configuration of Clippy. By default, any configuration will replace the default value. + #[lints(min_ident_chars)] + allowed_idents_below_min_chars: FxHashSet = + DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(), + /// List of prefixes to allow when determining whether an item's name ends with the module's name. + /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`), + /// then don't emit a warning. + /// + /// #### Example + /// + /// ```toml + /// allowed-prefixes = [ "to", "from" ] + /// ``` + /// + /// #### Noteworthy + /// + /// - By default, the following prefixes are allowed: `to`, `as`, `into`, `from`, `try_into` and `try_from` + /// - PascalCase variant is included automatically for each snake_case variant (e.g. if `try_into` is included, + /// `TryInto` will also be included) + /// - Use `".."` as part of the list to indicate that the configured values should be appended to the + /// default configuration of Clippy. By default, any configuration will replace the default value + #[lints(module_name_repetitions)] + allowed_prefixes: Vec = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(), + /// The list of unicode scripts allowed to be used in the scope. + #[lints(disallowed_script_idents)] + allowed_scripts: Vec = vec!["Latin".to_string()], + /// List of path segments allowed to have wildcard imports. /// + /// #### Example + /// + /// ```toml + /// allowed-wildcard-imports = [ "utils", "common" ] + /// ``` + /// + /// #### Noteworthy + /// + /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`. + /// 2. Paths with any segment that containing the word 'prelude' + /// are already allowed by default. + #[lints(wildcard_imports)] + allowed_wildcard_imports: FxHashSet = FxHashSet::default(), /// Suppress checking of the passed type names in all types of operations. /// /// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead. @@ -238,9 +338,8 @@ define_Conf! { /// /// A type, say `SomeType`, listed in this configuration has the same behavior of /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. - (arithmetic_side_effects_allowed: Vec = <_>::default()), - /// Lint: ARITHMETIC_SIDE_EFFECTS. - /// + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed: Vec = <_>::default(), /// Suppress checking of the passed type pair names in binary operations like addition or /// multiplication. /// @@ -255,9 +354,8 @@ define_Conf! { /// ```toml /// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]] /// ``` - (arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default()), - /// Lint: ARITHMETIC_SIDE_EFFECTS. - /// + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default(), /// Suppress checking of the passed type names in unary operations like "negation" (`-`). /// /// #### Example @@ -265,298 +363,78 @@ define_Conf! { /// ```toml /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] /// ``` - (arithmetic_side_effects_allowed_unary: Vec = <_>::default()), - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN, NEEDLESS_PASS_BY_REF_MUT. - /// + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed_unary: Vec = <_>::default(), + /// The maximum allowed size for arrays on the stack + #[lints(large_const_arrays, large_stack_arrays)] + array_size_threshold: u64 = 512_000, /// Suppress lints whenever the suggested change would cause breakage for other crates. - (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, MANUAL_PATTERN_CHAR_COMPARISON, ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON, COLLAPSIBLE_MATCH. - /// - /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` - #[default_text = ""] - (msrv: Msrv = Msrv::empty()), + #[lints( + box_collection, + enum_variant_names, + large_types_passed_by_value, + linkedlist, + needless_pass_by_ref_mut, + option_option, + rc_buffer, + rc_mutex, + redundant_allocation, + single_call_fn, + trivially_copy_pass_by_ref, + unnecessary_box_returns, + unnecessary_wraps, + unused_self, + upper_case_acronyms, + vec_box, + wrong_self_convention, + )] + avoid_breaking_exported_api: bool = true, + /// The list of types which may not be held across an await point. + #[lints(await_holding_invalid_type)] + await_holding_invalid_types: Vec = Vec::new(), /// DEPRECATED LINT: BLACKLISTED_NAME. /// /// Use the Disallowed Names lint instead #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)] - (blacklisted_names: Vec = Vec::new()), - /// Lint: COGNITIVE_COMPLEXITY. - /// + blacklisted_names: Vec = Vec::new(), + /// For internal testing only, ignores the current `publish` settings in the Cargo manifest. + #[lints(cargo_common_metadata)] + cargo_ignore_publish: bool = false, + /// Whether to also run the listed lints on private items. + #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)] + check_private_items: bool = false, /// The maximum cognitive complexity a function can have - (cognitive_complexity_threshold: u64 = 25), - /// Lint: EXCESSIVE_NESTING. - /// - /// The maximum amount of nesting a block can reside in - (excessive_nesting_threshold: u64 = 0), + #[lints(cognitive_complexity)] + cognitive_complexity_threshold: u64 = 25, /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. /// /// Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)] - (cyclomatic_complexity_threshold: u64 = 25), - /// Lint: DISALLOWED_NAMES. - /// + cyclomatic_complexity_threshold: u64 = 25, + /// The list of disallowed macros, written as fully qualified paths. + #[lints(disallowed_macros)] + disallowed_macros: Vec = Vec::new(), + /// The list of disallowed methods, written as fully qualified paths. + #[lints(disallowed_methods)] + disallowed_methods: Vec = Vec::new(), /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value /// `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. - (disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()), - /// Lint: SEMICOLON_INSIDE_BLOCK. - /// - /// Whether to lint only if it's multiline. - (semicolon_inside_block_ignore_singleline: bool = false), - /// Lint: SEMICOLON_OUTSIDE_BLOCK. - /// - /// Whether to lint only if it's singleline. - (semicolon_outside_block_ignore_multiline: bool = false), - /// Lint: DOC_MARKDOWN. - /// + #[lints(disallowed_names)] + disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(), + /// The list of disallowed types, written as fully qualified paths. + #[lints(disallowed_types)] + disallowed_types: Vec = Vec::new(), /// The list of words this lint should not consider as identifiers needing ticks. The value /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. For example: /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. - (doc_valid_idents: FxHashSet = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()), - /// Lint: TOO_MANY_ARGUMENTS. - /// - /// The maximum number of argument a function or method can have - (too_many_arguments_threshold: u64 = 7), - /// Lint: TYPE_COMPLEXITY. - /// - /// The maximum complexity a type can have - (type_complexity_threshold: u64 = 250), - /// Lint: MANY_SINGLE_CHAR_NAMES. - /// - /// The maximum number of single char bindings a scope may have - (single_char_binding_names_threshold: u64 = 4), - /// Lint: BOXED_LOCAL, USELESS_VEC. - /// - /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap - (too_large_for_stack: u64 = 200), - /// Lint: ENUM_VARIANT_NAMES. - /// - /// The minimum number of enum variants for the lints about variant names to trigger - (enum_variant_name_threshold: u64 = 3), - /// Lint: STRUCT_FIELD_NAMES. - /// - /// The minimum number of struct fields for the lints about field names to trigger - (struct_field_name_threshold: u64 = 3), - /// Lint: LARGE_ENUM_VARIANT. - /// - /// The maximum size of an enum's variant to avoid box suggestion - (enum_variant_size_threshold: u64 = 200), - /// Lint: VERBOSE_BIT_MASK. - /// - /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' - (verbose_bit_mask_threshold: u64 = 1), - /// Lint: DECIMAL_LITERAL_REPRESENTATION. - /// - /// The lower bound for linting decimal literals - (literal_representation_threshold: u64 = 16384), - /// Lint: TRIVIALLY_COPY_PASS_BY_REF. - /// - /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by - /// reference. By default there is no limit - #[default_text = ""] - (trivial_copy_size_limit: Option = None), - /// Lint: LARGE_TYPES_PASSED_BY_VALUE. - /// - /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. - (pass_by_value_size_limit: u64 = 256), - /// Lint: TOO_MANY_LINES. - /// - /// The maximum number of lines a function or method can have - (too_many_lines_threshold: u64 = 100), - /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. - /// - /// The maximum allowed size for arrays on the stack - (array_size_threshold: u64 = 512_000), - /// Lint: LARGE_STACK_FRAMES. - /// - /// The maximum allowed stack size for functions in bytes - (stack_size_threshold: u64 = 512_000), - /// Lint: VEC_BOX. - /// - /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed - (vec_box_size_threshold: u64 = 4096), - /// Lint: TYPE_REPETITION_IN_BOUNDS. - /// - /// The maximum number of bounds a trait can have to be linted - (max_trait_bounds: u64 = 3), - /// Lint: STRUCT_EXCESSIVE_BOOLS. - /// - /// The maximum number of bool fields a struct can have - (max_struct_bools: u64 = 3), - /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. - /// - /// The maximum number of bool parameters a function can have - (max_fn_params_bools: u64 = 3), - /// Lint: WILDCARD_IMPORTS. - /// - /// Whether to allow certain wildcard imports (prelude, super in tests). - (warn_on_all_wildcard_imports: bool = false), - /// Lint: DISALLOWED_MACROS. - /// - /// The list of disallowed macros, written as fully qualified paths. - (disallowed_macros: Vec = Vec::new()), - /// Lint: DISALLOWED_METHODS. - /// - /// The list of disallowed methods, written as fully qualified paths. - (disallowed_methods: Vec = Vec::new()), - /// Lint: DISALLOWED_TYPES. - /// - /// The list of disallowed types, written as fully qualified paths. - (disallowed_types: Vec = Vec::new()), - /// Lint: UNREADABLE_LITERAL. - /// - /// Should the fraction of a decimal be linted to include separators. - (unreadable_literal_lint_fractions: bool = true), - /// Lint: UPPER_CASE_ACRONYMS. - /// - /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other - (upper_case_acronyms_aggressive: bool = false), - /// Lint: MANUAL_LET_ELSE. - /// - /// Whether the matches should be considered by the lint, and whether there should - /// be filtering for common types. - (matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes), - /// Lint: CARGO_COMMON_METADATA. - /// - /// For internal testing only, ignores the current `publish` settings in the Cargo manifest. - (cargo_ignore_publish: bool = false), - /// Lint: NONSTANDARD_MACRO_BRACES. - /// - /// Enforce the named macros always use the braces specified. - /// - /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro - /// could be used with a full path two `MacroMatcher`s have to be added one with the full path - /// `crate_name::macro_name` and one with just the macro name. - (standard_macro_braces: Vec = Vec::new()), - /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. - /// - /// The list of imports to always rename, a fully qualified path followed by the rename. - (enforced_import_renames: Vec = Vec::new()), - /// Lint: DISALLOWED_SCRIPT_IDENTS. - /// - /// The list of unicode scripts allowed to be used in the scope. - (allowed_scripts: Vec = vec!["Latin".to_string()]), - /// Lint: NON_SEND_FIELDS_IN_SEND_TY. - /// + #[lints(doc_markdown)] + doc_valid_idents: FxHashSet = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(), /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. - (enable_raw_pointer_heuristic_for_send: bool = true), - /// Lint: INDEX_REFUTABLE_SLICE. - /// - /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in - /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. - /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. - (max_suggested_slice_pattern_length: u64 = 3), - /// Lint: AWAIT_HOLDING_INVALID_TYPE. - (await_holding_invalid_types: Vec = Vec::new()), - /// Lint: LARGE_INCLUDE_FILE. - /// - /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes - (max_include_file_size: u64 = 1_000_000), - /// Lint: EXPECT_USED. - /// - /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` - (allow_expect_in_tests: bool = false), - /// Lint: UNWRAP_USED. - /// - /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` - (allow_unwrap_in_tests: bool = false), - /// Lint: PANIC. - /// - /// Whether `panic` should be allowed in test functions or `#[cfg(test)]` - (allow_panic_in_tests: bool = false), - /// Lint: DBG_MACRO. - /// - /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` - (allow_dbg_in_tests: bool = false), - /// Lint: PRINT_STDOUT, PRINT_STDERR. - /// - /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` - (allow_print_in_tests: bool = false), - /// Lint: USELESS_VEC. - /// - /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]` - (allow_useless_vec_in_tests: bool = false), - /// Lint: RESULT_LARGE_ERR. - /// - /// The maximum size of the `Err`-variant in a `Result` returned from a function - (large_error_threshold: u64 = 128), - /// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND, BORROW_INTERIOR_MUTABLE_CONST, DECLARE_INTERIOR_MUTABLE_CONST. - /// - /// A list of paths to types that should be treated as if they do not contain interior mutability - (ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()])), - /// Lint: UNINLINED_FORMAT_ARGS. - /// - /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` - (allow_mixed_uninlined_format_args: bool = true), - /// Lint: INDEXING_SLICING. - /// - /// Whether to suppress a restriction lint in constant code. In same - /// cases the restructured operation might not be unavoidable, as the - /// suggested counterparts are unavailable in constant code. This - /// configuration will cause restriction lints to trigger even - /// if no suggestion can be made. - (suppress_restriction_lint_in_const: bool = false), - /// Lint: MISSING_DOCS_IN_PRIVATE_ITEMS. - /// - /// Whether to **only** check for missing documentation in items visible within the current - /// crate. For example, `pub(crate)` items. - (missing_docs_in_crate_items: bool = false), - /// Lint: LARGE_FUTURES. - /// - /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint - (future_size_threshold: u64 = 16 * 1024), - /// Lint: UNNECESSARY_BOX_RETURNS. - /// - /// The byte size a `T` in `Box` can have, below which it triggers the `clippy::unnecessary_box` lint - (unnecessary_box_size: u64 = 128), - /// Lint: MODULE_INCEPTION. - /// - /// Whether to allow module inception if it's not public. - (allow_private_module_inception: bool = false), - /// Lint: MIN_IDENT_CHARS. - /// - /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of - /// the list to indicate, that the configured values should be appended to the default - /// configuration of Clippy. By default, any configuration will replace the default value. - (allowed_idents_below_min_chars: FxHashSet = - DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect()), - /// Lint: MIN_IDENT_CHARS. - /// - /// Minimum chars an ident can have, anything below or equal to this will be linted. - (min_ident_chars_threshold: u64 = 1), - /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS. - /// - /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block - (accept_comment_above_statement: bool = true), - /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS. - /// - /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block - (accept_comment_above_attributes: bool = true), - /// Lint: UNNECESSARY_RAW_STRING_HASHES. - /// - /// Whether to allow `r#""#` when `r""` can be used - (allow_one_hash_in_raw_strings: bool = false), - /// Lint: ABSOLUTE_PATHS. - /// - /// The maximum number of segments a path can have before being linted, anything above this will - /// be linted. - (absolute_paths_max_segments: u64 = 2), - /// Lint: ABSOLUTE_PATHS. - /// - /// Which crates to allow absolute paths from - (absolute_paths_allowed_crates: FxHashSet = FxHashSet::default()), - /// Lint: PATH_ENDS_WITH_EXT. - /// - /// Additional dotfiles (files or directories starting with a dot) to allow - (allowed_dotfiles: Vec = Vec::default()), - /// Lint: MULTIPLE_CRATE_VERSIONS. - /// - /// A list of crate names to allow duplicates of - (allowed_duplicate_crates: FxHashSet = FxHashSet::default()), - /// Lint: EXPLICIT_ITER_LOOP. - /// + #[lints(non_send_fields_in_send_ty)] + enable_raw_pointer_heuristic_for_send: bool = true, /// Whether to recommend using implicit into iter for reborrowed values. /// /// #### Example @@ -574,77 +452,195 @@ define_Conf! { /// for _ in &*rmvec {} /// for _ in &mut *rmvec {} /// ``` - (enforce_iter_loop_reborrow: bool = false), - /// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC. - /// - /// Whether to also run the listed lints on private items. - (check_private_items: bool = false), - /// Lint: PUB_UNDERSCORE_FIELDS. - /// + #[lints(explicit_iter_loop)] + enforce_iter_loop_reborrow: bool = false, + /// The list of imports to always rename, a fully qualified path followed by the rename. + #[lints(missing_enforced_import_renames)] + enforced_import_renames: Vec = Vec::new(), + /// The minimum number of enum variants for the lints about variant names to trigger + #[lints(enum_variant_names)] + enum_variant_name_threshold: u64 = 3, + /// The maximum size of an enum's variant to avoid box suggestion + #[lints(large_enum_variant)] + enum_variant_size_threshold: u64 = 200, + /// The maximum amount of nesting a block can reside in + #[lints(excessive_nesting)] + excessive_nesting_threshold: u64 = 0, + /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint + #[lints(large_futures)] + future_size_threshold: u64 = 16 * 1024, + /// A list of paths to types that should be treated as if they do not contain interior mutability + #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)] + ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()]), + /// The maximum size of the `Err`-variant in a `Result` returned from a function + #[lints(result_large_err)] + large_error_threshold: u64 = 128, + /// The lower bound for linting decimal literals + #[lints(decimal_literal_representation)] + literal_representation_threshold: u64 = 16384, + /// Whether the matches should be considered by the lint, and whether there should + /// be filtering for common types. + #[lints(manual_let_else)] + matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes, + /// The maximum number of bool parameters a function can have + #[lints(fn_params_excessive_bools)] + max_fn_params_bools: u64 = 3, + /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes + #[lints(large_include_file)] + max_include_file_size: u64 = 1_000_000, + /// The maximum number of bool fields a struct can have + #[lints(struct_excessive_bools)] + max_struct_bools: u64 = 3, + /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in + /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. + /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. + #[lints(index_refutable_slice)] + max_suggested_slice_pattern_length: u64 = 3, + /// The maximum number of bounds a trait can have to be linted + #[lints(type_repetition_in_bounds)] + max_trait_bounds: u64 = 3, + /// Minimum chars an ident can have, anything below or equal to this will be linted. + #[lints(min_ident_chars)] + min_ident_chars_threshold: u64 = 1, + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + #[lints(missing_docs_in_private_items)] + missing_docs_in_crate_items: bool = false, + /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` + #[default_text = "current version"] + #[lints( + allow_attributes, + allow_attributes_without_reason, + almost_complete_range, + approx_constant, + assigning_clones, + borrow_as_ptr, + cast_abs_to_unsigned, + checked_conversions, + cloned_instead_of_copied, + collapsible_match, + collapsible_str_replace, + deprecated_cfg_attr, + derivable_impls, + err_expect, + filter_map_next, + from_over_into, + if_then_some_else_none, + index_refutable_slice, + iter_kv_map, + legacy_numeric_constants, + manual_bits, + manual_c_str_literals, + manual_clamp, + manual_hash_one, + manual_is_ascii_check, + manual_let_else, + manual_non_exhaustive, + manual_pattern_char_comparison, + manual_range_contains, + manual_rem_euclid, + manual_retain, + manual_split_once, + manual_str_repeat, + manual_strip, + manual_try_fold, + map_clone, + map_unwrap_or, + match_like_matches_macro, + mem_replace_with_default, + missing_const_for_fn, + needless_borrow, + option_as_ref_deref, + option_map_unwrap_or, + ptr_as_ptr, + redundant_field_names, + redundant_static_lifetimes, + seek_from_current, + seek_rewind, + transmute_ptr_to_ref, + tuple_array_conversions, + type_repetition_in_bounds, + unchecked_duration_subtraction, + uninlined_format_args, + unnecessary_lazy_evaluations, + unnested_or_patterns, + use_self, + )] + msrv: Msrv = Msrv::empty(), + /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. + #[lints(large_types_passed_by_value)] + pass_by_value_size_limit: u64 = 256, /// Lint "public" fields in a struct that are prefixed with an underscore based on their /// exported visibility, or whether they are marked as "pub". - (pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported), - /// Lint: MODULO_ARITHMETIC. - /// - /// Don't lint when comparing the result of a modulo operation to zero. - (allow_comparison_to_zero: bool = true), - /// Lint: WILDCARD_IMPORTS. - /// - /// List of path segments allowed to have wildcard imports. - /// - /// #### Example - /// - /// ```toml - /// allowed-wildcard-imports = [ "utils", "common" ] - /// ``` - /// - /// #### Noteworthy - /// - /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`. - /// 2. Paths with any segment that containing the word 'prelude' - /// are already allowed by default. - (allowed_wildcard_imports: FxHashSet = FxHashSet::default()), - /// Lint: MODULE_NAME_REPETITIONS. - /// - /// List of prefixes to allow when determining whether an item's name ends with the module's name. - /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`), - /// then don't emit a warning. - /// - /// #### Example - /// - /// ```toml - /// allowed-prefixes = [ "to", "from" ] - /// ``` - /// - /// #### Noteworthy - /// - /// - By default, the following prefixes are allowed: `to`, `as`, `into`, `from`, `try_into` and `try_from` - /// - PascalCase variant is included automatically for each snake_case variant (e.g. if `try_into` is included, - /// `TryInto` will also be included) - /// - Use `".."` as part of the list to indicate that the configured values should be appended to the - /// default configuration of Clippy. By default, any configuration will replace the default value - (allowed_prefixes: Vec = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()), - /// Lint: RENAMED_FUNCTION_PARAMS. - /// - /// List of trait paths to ignore when checking renamed function parameters. - /// - /// #### Example - /// - /// ```toml - /// allow-renamed-params-for = [ "std::convert::From" ] - /// ``` - /// - /// #### Noteworthy - /// - /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` - /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the - /// default configuration of Clippy. By default, any configuration will replace the default value. - (allow_renamed_params_for: Vec = - DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect()), - /// Lint: MACRO_METAVARS_IN_UNSAFE. + #[lints(pub_underscore_fields)] + pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported, + /// Whether to lint only if it's multiline. + #[lints(semicolon_inside_block)] + semicolon_inside_block_ignore_singleline: bool = false, + /// Whether to lint only if it's singleline. + #[lints(semicolon_outside_block)] + semicolon_outside_block_ignore_multiline: bool = false, + /// The maximum number of single char bindings a scope may have + #[lints(many_single_char_names)] + single_char_binding_names_threshold: u64 = 4, + /// The maximum allowed stack size for functions in bytes + #[lints(large_stack_frames)] + stack_size_threshold: u64 = 512_000, + /// Enforce the named macros always use the braces specified. /// + /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro + /// could be used with a full path two `MacroMatcher`s have to be added one with the full path + /// `crate_name::macro_name` and one with just the macro name. + #[lints(nonstandard_macro_braces)] + standard_macro_braces: Vec = Vec::new(), + /// The minimum number of struct fields for the lints about field names to trigger + #[lints(struct_field_names)] + struct_field_name_threshold: u64 = 3, + /// Whether to suppress a restriction lint in constant code. In same + /// cases the restructured operation might not be unavoidable, as the + /// suggested counterparts are unavailable in constant code. This + /// configuration will cause restriction lints to trigger even + /// if no suggestion can be made. + #[lints(indexing_slicing)] + suppress_restriction_lint_in_const: bool = false, + /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap + #[lints(boxed_local, useless_vec)] + too_large_for_stack: u64 = 200, + /// The maximum number of argument a function or method can have + #[lints(too_many_arguments)] + too_many_arguments_threshold: u64 = 7, + /// The maximum number of lines a function or method can have + #[lints(too_many_lines)] + too_many_lines_threshold: u64 = 100, + /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by + /// reference. By default there is no limit + #[default_text = "target_pointer_width * 2"] + #[lints(trivially_copy_pass_by_ref)] + trivial_copy_size_limit: Option = None, + /// The maximum complexity a type can have + #[lints(type_complexity)] + type_complexity_threshold: u64 = 250, + /// The byte size a `T` in `Box` can have, below which it triggers the `clippy::unnecessary_box` lint + #[lints(unnecessary_box_returns)] + unnecessary_box_size: u64 = 128, + /// Should the fraction of a decimal be linted to include separators. + #[lints(unreadable_literal)] + unreadable_literal_lint_fractions: bool = true, + /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other + #[lints(upper_case_acronyms)] + upper_case_acronyms_aggressive: bool = false, + /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed + #[lints(vec_box)] + vec_box_size_threshold: u64 = 4096, + /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' + #[lints(verbose_bit_mask)] + verbose_bit_mask_threshold: u64 = 1, + /// Whether to allow certain wildcard imports (prelude, super in tests). + #[lints(wildcard_imports)] + warn_on_all_wildcard_imports: bool = false, /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros. - (warn_unsafe_macro_metavars_in_private_macros: bool = false), + #[lints(macro_metavars_in_unsafe)] + warn_unsafe_macro_metavars_in_private_macros: bool = false, } /// Search for the configuration file. diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index ff7fa7241cb96..d2246f12029b3 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -1,5 +1,4 @@ -#![feature(rustc_private, let_chains)] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![feature(rustc_private, array_windows, let_chains)] #![warn( trivial_casts, trivial_numeric_casts, diff --git a/clippy_config/src/metadata.rs b/clippy_config/src/metadata.rs index 400887185e8cf..7cbd92a9b7100 100644 --- a/clippy_config/src/metadata.rs +++ b/clippy_config/src/metadata.rs @@ -1,11 +1,12 @@ -use std::fmt::{self, Write}; +use itertools::Itertools; +use std::fmt; #[derive(Debug, Clone, Default)] pub struct ClippyConfiguration { pub name: String, pub default: String, - pub lints: Vec, - pub doc: String, + pub lints: &'static [&'static str], + pub doc: &'static str, pub deprecation_reason: Option<&'static str>, } @@ -13,61 +14,23 @@ impl fmt::Display for ClippyConfiguration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "- `{}`: {}", self.name, self.doc)?; if !self.default.is_empty() { - write!(f, " (default: `{}`)", self.default)?; + write!(f, "\n\n (default: `{}`)", self.default)?; } Ok(()) } } impl ClippyConfiguration { - pub fn new( - name: &'static str, - default: String, - doc_comment: &'static str, - deprecation_reason: Option<&'static str>, - ) -> Self { - let (mut lints, doc) = parse_config_field_doc(doc_comment) - .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); - - lints.sort(); - - Self { - name: to_kebab(name), - lints, - doc, - default, - deprecation_reason, - } - } - pub fn to_markdown_paragraph(&self) -> String { - let mut out = format!( - "## `{}`\n{}\n\n", + format!( + "## `{}`\n{}\n\n**Default Value:** `{}`\n\n---\n**Affected lints:**\n{}\n\n", self.name, - self.doc - .lines() - .map(|line| line.strip_prefix(" ").unwrap_or(line)) - .collect::>() - .join("\n"), - ); - - if !self.default.is_empty() { - write!(out, "**Default Value:** `{}`\n\n", self.default).unwrap(); - } - - write!( - out, - "---\n**Affected lints:**\n{}\n\n", - self.lints - .iter() - .map(|name| name.to_string().split_whitespace().next().unwrap().to_string()) - .map(|name| format!("* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})")) - .collect::>() - .join("\n"), + self.doc.lines().map(|x| x.strip_prefix(' ').unwrap_or(x)).join("\n"), + self.default, + self.lints.iter().format_with("\n", |name, f| f(&format_args!( + "* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})" + ))), ) - .unwrap(); - - out } pub fn to_markdown_link(&self) -> String { @@ -75,47 +38,3 @@ impl ClippyConfiguration { format!("[`{}`]: {BOOK_CONFIGS_PATH}#{}", self.name, self.name) } } - -/// This parses the field documentation of the config struct. -/// -/// ```rust, ignore -/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin") -/// ``` -/// -/// Would yield: -/// ```rust, ignore -/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin") -/// ``` -fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec, String)> { - const DOC_START: &str = " Lint: "; - if doc_comment.starts_with(DOC_START) - && let Some(split_pos) = doc_comment.find('.') - { - let mut doc_comment = doc_comment.to_string(); - let mut documentation = doc_comment.split_off(split_pos); - - // Extract lints - doc_comment.make_ascii_lowercase(); - let lints: Vec = doc_comment - .split_off(DOC_START.len()) - .lines() - .next() - .unwrap() - .split(", ") - .map(str::to_string) - .collect(); - - // Format documentation correctly - // split off leading `.` from lint name list and indent for correct formatting - documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n "); - - Some((lints, documentation)) - } else { - None - } -} - -/// Transforms a given `snake_case_string` to a tasty `kebab-case-string` -fn to_kebab(config_name: &str) -> String { - config_name.replace('_', "-") -} diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 4104e7d94f146..a5d72c3a559df 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -13,9 +13,6 @@ opener = "0.6" shell-escape = "0.1" walkdir = "2.3" -[features] -deny-warnings = [] - [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] rustc_private = true diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 2562314418172..5fc4365c6e785 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,30 +1,65 @@ use crate::clippy_project_root; use itertools::Itertools; +use rustc_lexer::{tokenize, TokenKind}; use shell_escape::escape; use std::ffi::{OsStr, OsString}; -use std::path::Path; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; use std::{fs, io}; use walkdir::WalkDir; -#[derive(Debug)] -pub enum CliError { +pub enum Error { CommandFailed(String, String), - IoError(io::Error), + Io(io::Error), RustfmtNotInstalled, - WalkDirError(walkdir::Error), + WalkDir(walkdir::Error), IntellijSetupActive, + Parse(PathBuf, usize, String), + CheckFailed, } -impl From for CliError { +impl From for Error { fn from(error: io::Error) -> Self { - Self::IoError(error) + Self::Io(error) } } -impl From for CliError { +impl From for Error { fn from(error: walkdir::Error) -> Self { - Self::WalkDirError(error) + Self::WalkDir(error) + } +} + +impl Error { + fn display(&self) { + match self { + Self::CheckFailed => { + eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update."); + }, + Self::CommandFailed(command, stderr) => { + eprintln!("error: command `{command}` failed!\nstderr: {stderr}"); + }, + Self::Io(err) => { + eprintln!("error: {err}"); + }, + Self::RustfmtNotInstalled => { + eprintln!("error: rustfmt nightly is not installed."); + }, + Self::WalkDir(err) => { + eprintln!("error: {err}"); + }, + Self::IntellijSetupActive => { + eprintln!( + "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\ + Not formatting because that would format the local repo as well!\n\ + Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`." + ); + }, + Self::Parse(path, line, msg) => { + eprintln!("error parsing `{}:{line}`: {msg}", path.display()); + }, + } } } @@ -34,75 +69,244 @@ struct FmtContext { rustfmt_path: String, } -// the "main" function of cargo dev fmt -pub fn run(check: bool, verbose: bool) { - fn try_run(context: &FmtContext) -> Result { - let mut success = true; - - let project_root = clippy_project_root(); - - // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to - // format because rustfmt would also format the entire rustc repo as it is a local - // dependency - if fs::read_to_string(project_root.join("Cargo.toml")) - .expect("Failed to read clippy Cargo.toml") - .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") - { - return Err(CliError::IntellijSetupActive); - } - - rustfmt_test(context)?; +struct ClippyConf<'a> { + name: &'a str, + attrs: &'a str, + lints: Vec<&'a str>, + field: &'a str, +} - success &= cargo_fmt(context, project_root.as_path())?; - success &= cargo_fmt(context, &project_root.join("clippy_dev"))?; - success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?; - success &= cargo_fmt(context, &project_root.join("lintcheck"))?; +fn offset_to_line(text: &str, offset: usize) -> usize { + match text.split('\n').try_fold((1usize, 0usize), |(line, pos), s| { + let pos = pos + s.len() + 1; + if pos > offset { + ControlFlow::Break(line) + } else { + ControlFlow::Continue((line + 1, pos)) + } + }) { + ControlFlow::Break(x) | ControlFlow::Continue((x, _)) => x, + } +} - let chunks = WalkDir::new(project_root.join("tests")) - .into_iter() - .filter_map(|entry| { - let entry = entry.expect("failed to find tests"); - let path = entry.path(); +/// Formats the configuration list in `clippy_config/src/conf.rs` +#[expect(clippy::too_many_lines)] +fn fmt_conf(check: bool) -> Result<(), Error> { + #[derive(Clone, Copy)] + enum State { + Start, + Docs, + Pound, + OpenBracket, + Attr(u32), + Lints, + EndLints, + Field, + } - if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { - None - } else { - Some(entry.into_path().into_os_string()) - } - }) - .chunks(250); + let path: PathBuf = [ + clippy_project_root().as_path(), + "clippy_config".as_ref(), + "src".as_ref(), + "conf.rs".as_ref(), + ] + .into_iter() + .collect(); + let text = fs::read_to_string(&path)?; - for chunk in &chunks { - success &= rustfmt(context, chunk)?; - } + let (pre, conf) = text + .split_once("define_Conf! {\n") + .expect("can't find config definition"); + let (conf, post) = conf.split_once("\n}\n").expect("can't find config definition"); + let conf_offset = pre.len() + 15; - Ok(success) - } + let mut pos = 0u32; + let mut attrs_start = 0; + let mut attrs_end = 0; + let mut field_start = 0; + let mut lints = Vec::new(); + let mut name = ""; + let mut fields = Vec::new(); + let mut state = State::Start; - fn output_err(err: CliError) { - match err { - CliError::CommandFailed(command, stderr) => { - eprintln!("error: A command failed! `{command}`\nstderr: {stderr}"); + for (i, t) in tokenize(conf) + .map(|x| { + let start = pos; + pos += x.len; + (start as usize, x) + }) + .filter(|(_, t)| !matches!(t.kind, TokenKind::Whitespace)) + { + match (state, t.kind) { + (State::Start, TokenKind::LineComment { doc_style: Some(_) }) => { + attrs_start = i; + attrs_end = i + t.len as usize; + state = State::Docs; }, - CliError::IoError(err) => { - eprintln!("error: {err}"); + (State::Start, TokenKind::Pound) => { + attrs_start = i; + attrs_end = i; + state = State::Pound; }, - CliError::RustfmtNotInstalled => { - eprintln!("error: rustfmt nightly is not installed."); + (State::Docs, TokenKind::LineComment { doc_style: Some(_) }) => attrs_end = i + t.len as usize, + (State::Docs, TokenKind::Pound) => state = State::Pound, + (State::Pound, TokenKind::OpenBracket) => state = State::OpenBracket, + (State::OpenBracket, TokenKind::Ident) => { + state = if conf[i..i + t.len as usize] == *"lints" { + State::Lints + } else { + State::Attr(0) + }; }, - CliError::WalkDirError(err) => { - eprintln!("error: {err}"); + (State::Attr(0), TokenKind::CloseBracket) => { + attrs_end = i + 1; + state = State::Docs; }, - CliError::IntellijSetupActive => { - eprintln!( - "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`. -Not formatting because that would format the local repo as well! -Please revert the changes to Cargo.tomls with `cargo dev remove intellij`." - ); + (State::Attr(x), TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace) => { + state = State::Attr(x + 1); + }, + (State::Attr(x), TokenKind::CloseParen | TokenKind::CloseBracket | TokenKind::CloseBrace) => { + state = State::Attr(x - 1); + }, + (State::Lints, TokenKind::Ident) => lints.push(&conf[i..i + t.len as usize]), + (State::Lints, TokenKind::CloseBracket) => state = State::EndLints, + (State::EndLints | State::Docs, TokenKind::Ident) => { + field_start = i; + name = &conf[i..i + t.len as usize]; + state = State::Field; + }, + (State::Field, TokenKind::LineComment { doc_style: Some(_) }) => { + #[expect(clippy::drain_collect)] + fields.push(ClippyConf { + name, + lints: lints.drain(..).collect(), + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..i].trim_end(), + }); + attrs_start = i; + attrs_end = i + t.len as usize; + state = State::Docs; + }, + (State::Field, TokenKind::Pound) => { + #[expect(clippy::drain_collect)] + fields.push(ClippyConf { + name, + lints: lints.drain(..).collect(), + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..i].trim_end(), + }); + attrs_start = i; + attrs_end = i; + state = State::Pound; + }, + (State::Field | State::Attr(_), _) + | (State::Lints, TokenKind::Comma | TokenKind::OpenParen | TokenKind::CloseParen) => {}, + _ => { + return Err(Error::Parse( + path, + offset_to_line(&text, conf_offset + i), + format!("unexpected token `{}`", &conf[i..i + t.len as usize]), + )); }, } } + if !matches!(state, State::Field) { + return Err(Error::Parse( + path, + offset_to_line(&text, conf_offset + conf.len()), + "incomplete field".into(), + )); + } + fields.push(ClippyConf { + name, + lints, + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..].trim_end(), + }); + + for field in &mut fields { + field.lints.sort_unstable(); + } + fields.sort_by_key(|x| x.name); + + let new_text = format!( + "{pre}define_Conf! {{\n{}}}\n{post}", + fields.iter().format_with("", |field, f| { + if field.lints.is_empty() { + f(&format_args!(" {}\n {}\n", field.attrs, field.field)) + } else if field.lints.iter().map(|x| x.len() + 2).sum::() < 120 - 14 { + f(&format_args!( + " {}\n #[lints({})]\n {}\n", + field.attrs, + field.lints.iter().join(", "), + field.field, + )) + } else { + f(&format_args!( + " {}\n #[lints({}\n )]\n {}\n", + field.attrs, + field + .lints + .iter() + .format_with("", |x, f| f(&format_args!("\n {x},"))), + field.field, + )) + } + }) + ); + + if text != new_text { + if check { + return Err(Error::CheckFailed); + } + fs::write(&path, new_text.as_bytes())?; + } + Ok(()) +} + +fn run_rustfmt(context: &FmtContext) -> Result<(), Error> { + let project_root = clippy_project_root(); + + // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to + // format because rustfmt would also format the entire rustc repo as it is a local + // dependency + if fs::read_to_string(project_root.join("Cargo.toml")) + .expect("Failed to read clippy Cargo.toml") + .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") + { + return Err(Error::IntellijSetupActive); + } + + check_for_rustfmt(context)?; + + cargo_fmt(context, project_root.as_path())?; + cargo_fmt(context, &project_root.join("clippy_dev"))?; + cargo_fmt(context, &project_root.join("rustc_tools_util"))?; + cargo_fmt(context, &project_root.join("lintcheck"))?; + + let chunks = WalkDir::new(project_root.join("tests")) + .into_iter() + .filter_map(|entry| { + let entry = entry.expect("failed to find tests"); + let path = entry.path(); + + if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { + None + } else { + Some(entry.into_path().into_os_string()) + } + }) + .chunks(250); + + for chunk in &chunks { + rustfmt(context, chunk)?; + } + Ok(()) +} + +// the "main" function of cargo dev fmt +pub fn run(check: bool, verbose: bool) { let output = Command::new("rustup") .args(["which", "rustfmt"]) .stderr(Stdio::inherit()) @@ -120,21 +324,10 @@ Please revert the changes to Cargo.tomls with `cargo dev remove intellij`." verbose, rustfmt_path, }; - let result = try_run(&context); - let code = match result { - Ok(true) => 0, - Ok(false) => { - eprintln!(); - eprintln!("Formatting check failed."); - eprintln!("Run `cargo dev fmt` to update formatting."); - 1 - }, - Err(err) => { - output_err(err); - 1 - }, - }; - process::exit(code); + if let Err(e) = run_rustfmt(&context).and_then(|()| fmt_conf(check)) { + e.display(); + process::exit(1); + } } fn format_command(program: impl AsRef, dir: impl AsRef, args: &[impl AsRef]) -> String { @@ -148,12 +341,12 @@ fn format_command(program: impl AsRef, dir: impl AsRef, args: &[imp ) } -fn exec( +fn exec_fmt_command( context: &FmtContext, program: impl AsRef, dir: impl AsRef, args: &[impl AsRef], -) -> Result { +) -> Result<(), Error> { if context.verbose { println!("{}", format_command(&program, &dir, args)); } @@ -166,28 +359,28 @@ fn exec( .unwrap(); let success = output.status.success(); - if !context.check && !success { - let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); - return Err(CliError::CommandFailed( - format_command(&program, &dir, args), - String::from(stderr), - )); + match (context.check, success) { + (_, true) => Ok(()), + (true, false) => Err(Error::CheckFailed), + (false, false) => { + let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); + Err(Error::CommandFailed( + format_command(&program, &dir, args), + String::from(stderr), + )) + }, } - - Ok(success) } -fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { +fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<(), Error> { let mut args = vec!["fmt", "--all"]; if context.check { args.push("--check"); } - let success = exec(context, "cargo", path, &args)?; - - Ok(success) + exec_fmt_command(context, "cargo", path, &args) } -fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { +fn check_for_rustfmt(context: &FmtContext) -> Result<(), Error> { let program = "rustfmt"; let dir = std::env::current_dir()?; let args = &["--version"]; @@ -204,23 +397,20 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { .unwrap_or("") .starts_with("error: 'rustfmt' is not installed") { - Err(CliError::RustfmtNotInstalled) + Err(Error::RustfmtNotInstalled) } else { - Err(CliError::CommandFailed( + Err(Error::CommandFailed( format_command(program, &dir, args), std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), )) } } -fn rustfmt(context: &FmtContext, paths: impl Iterator) -> Result { +fn rustfmt(context: &FmtContext, paths: impl Iterator) -> Result<(), Error> { let mut args = Vec::new(); if context.check { args.push(OsString::from("--check")); } args.extend(paths); - - let success = exec(context, &context.rustfmt_path, std::env::current_dir()?, &args)?; - - Ok(success) + exec_fmt_command(context, &context.rustfmt_path, std::env::current_dir()?, &args) } diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3aa43dbe23ed6..ad385d5fbd29f 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,6 +1,5 @@ #![feature(let_chains)] #![feature(rustc_private)] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn( trivial_casts, trivial_numeric_casts, diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 366b52b25dfcf..755b04b0b232f 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -1,4 +1,3 @@ -#![cfg_attr(feature = "deny-warnings", deny(warnings))] // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] @@ -74,7 +73,7 @@ fn main() { new_name, uplift, } => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift), - DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()), + DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, &reason), } } @@ -223,7 +222,7 @@ enum DevCommand { name: String, #[arg(long, short)] /// The reason for deprecation - reason: Option, + reason: String, }, } diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 4a4261d1a1e63..19560b31fd3e2 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -1,10 +1,14 @@ -use std::ffi::OsStr; -use std::num::ParseIntError; use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; use std::{env, thread}; +#[cfg(windows)] +const PYTHON: &str = "python"; + +#[cfg(not(windows))] +const PYTHON: &str = "python3"; + /// # Panics /// /// Panics if the python commands could not be spawned @@ -25,7 +29,7 @@ pub fn run(port: u16, lint: Option) -> ! { } if let Some(url) = url.take() { thread::spawn(move || { - Command::new("python3") + Command::new(PYTHON) .arg("-m") .arg("http.server") .arg(port.to_string()) @@ -58,8 +62,3 @@ fn mtime(path: impl AsRef) -> SystemTime { .unwrap_or(SystemTime::UNIX_EPOCH) } } - -#[allow(clippy::missing_errors_doc)] -pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> { - arg.to_string_lossy().parse::().map(|_| ()) -} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 45353901c98fd..15578d69c3a9d 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,12 @@ use crate::clippy_project_root; use aho_corasick::AhoCorasickBuilder; -use indoc::writedoc; use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::fmt::{self, Write}; use std::fs::{self, OpenOptions}; -use std::io::{self, Read, Seek, SeekFrom, Write as _}; +use std::io::{self, Read, Seek, Write as _}; use std::ops::Range; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; @@ -77,12 +76,8 @@ fn generate_lint_files( for lint in usable_lints .iter() .map(|l| &*l.name) - .chain(deprecated_lints.iter().map(|l| &*l.name)) - .chain( - renamed_lints - .iter() - .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)), - ) + .chain(deprecated_lints.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) + .chain(renamed_lints.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) .sorted() { writeln!(res, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); @@ -108,11 +103,6 @@ fn generate_lint_files( update_mode, &gen_declared_lints(internal_lints.iter(), usable_lints.iter()), ); - process_file( - "clippy_lints/src/lib.deprecated.rs", - update_mode, - &gen_deprecated(deprecated_lints), - ); let content = gen_deprecated_lints_test(deprecated_lints); process_file("tests/ui/deprecated.rs", update_mode, &content); @@ -205,7 +195,7 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { let ext = f.path().extension(); (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) && name != Some(OsStr::new("rename.rs")) - && name != Some(OsStr::new("renamed_lints.rs")) + && name != Some(OsStr::new("deprecated_lints.rs")) }) { rewrite_file(file.path(), |s| { @@ -213,6 +203,19 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { }); } + let version = crate::new_lint::get_stabilization_version(); + rewrite_file(Path::new("clippy_lints/src/deprecated_lints.rs"), |s| { + insert_at_marker( + s, + "// end renamed lints. used by `cargo dev rename_lint`", + &format!( + "#[clippy::version = \"{version}\"]\n \ + (\"{}\", \"{}\"),\n ", + lint.old_name, lint.new_name, + ), + ) + }); + renamed_lints.push(lint); renamed_lints.sort_by(|lhs, rhs| { lhs.new_name @@ -222,11 +225,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { .then_with(|| lhs.old_name.cmp(&rhs.old_name)) }); - write_file( - Path::new("clippy_lints/src/renamed_lints.rs"), - &gen_renamed_lints_list(&renamed_lints), - ); - if uplift { write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); println!( @@ -293,7 +291,8 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being // renamed. - for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { + for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("deprecated_lints.rs")) + { rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); } @@ -304,7 +303,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { println!("note: `cargo uitest` still needs to be run to update the test results"); } -const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; /// Runs the `deprecate` command /// /// This does the following: @@ -314,33 +312,16 @@ const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; /// # Panics /// /// If a file path could not read from or written to -pub fn deprecate(name: &str, reason: Option<&str>) { - fn finish( - (lints, mut deprecated_lints, renamed_lints): (Vec, Vec, Vec), - name: &str, - reason: &str, - ) { - deprecated_lints.push(DeprecatedLint { - name: name.to_string(), - reason: reason.to_string(), - declaration_range: Range::default(), - }); - - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{name}` has successfully been deprecated"); - - if reason == DEFAULT_DEPRECATION_REASON { - println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`"); - } - println!("note: you must run `cargo uitest` to update the test results"); - } - - let reason = reason.unwrap_or(DEFAULT_DEPRECATION_REASON); - let name_lower = name.to_lowercase(); - let name_upper = name.to_uppercase(); +pub fn deprecate(name: &str, reason: &str) { + let prefixed_name = if name.starts_with("clippy::") { + name.to_owned() + } else { + format!("clippy::{name}") + }; + let stripped_name = &prefixed_name[8..]; - let (mut lints, deprecated_lints, renamed_lints) = gather_all(); - let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { + let (mut lints, mut deprecated_lints, renamed_lints) = gather_all(); + let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else { eprintln!("error: failed to find lint `{name}`"); return; }; @@ -357,13 +338,27 @@ pub fn deprecate(name: &str, reason: Option<&str>) { let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); - if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { - declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); - finish((lints, deprecated_lints, renamed_lints), name, reason); - return; - } + if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) { + let version = crate::new_lint::get_stabilization_version(); + rewrite_file(deprecated_lints_path, |s| { + insert_at_marker( + s, + "// end deprecated lints. used by `cargo dev deprecate_lint`", + &format!("#[clippy::version = \"{version}\"]\n (\"{prefixed_name}\", \"{reason}\"),\n ",), + ) + }); + + deprecated_lints.push(DeprecatedLint { + name: prefixed_name, + reason: reason.into(), + }); - eprintln!("error: lint not found"); + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); + } else { + eprintln!("error: lint not found"); + } } fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { @@ -377,14 +372,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io // Some lints have their own directories, delete them if path.is_dir() { - fs::remove_dir_all(path).ok(); + let _ = fs::remove_dir_all(path); return; } // Remove all related test files - fs::remove_file(path.with_extension("rs")).ok(); - fs::remove_file(path.with_extension("stderr")).ok(); - fs::remove_file(path.with_extension("fixed")).ok(); + let _ = fs::remove_file(path.with_extension("rs")); + let _ = fs::remove_file(path.with_extension("stderr")); + let _ = fs::remove_file(path.with_extension("fixed")); } fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { @@ -427,7 +422,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io lint_mod_path.set_file_name(name); lint_mod_path.set_extension("rs"); - fs::remove_file(lint_mod_path).ok(); + let _ = fs::remove_file(lint_mod_path); } let mut content = @@ -465,37 +460,6 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io Ok(false) } -fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { - let mut file = OpenOptions::new().write(true).open(path)?; - - file.seek(SeekFrom::End(0))?; - - let version = crate::new_lint::get_stabilization_version(); - let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON { - "TODO" - } else { - reason - }; - - writedoc!( - file, - " - - declare_deprecated_lint! {{ - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// {deprecation_reason} - #[clippy::version = \"{version}\"] - pub {name}, - \"{reason}\" - }} - - " - ) -} - /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there /// were no replacements. fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option { @@ -604,14 +568,12 @@ impl Lint { struct DeprecatedLint { name: String, reason: String, - declaration_range: Range, } impl DeprecatedLint { - fn new(name: &str, reason: &str, declaration_range: Range) -> Self { + fn new(name: &str, reason: &str) -> Self { Self { - name: name.to_lowercase(), + name: remove_line_splices(name), reason: remove_line_splices(reason), - declaration_range, } } } @@ -629,28 +591,6 @@ impl RenamedLint { } } -/// Generates the `register_removed` code -#[must_use] -fn gen_deprecated(lints: &[DeprecatedLint]) -> String { - let mut output = GENERATED_FILE_COMMENT.to_string(); - output.push_str("{\n"); - for lint in lints { - let _: fmt::Result = write!( - output, - concat!( - " store.register_removed(\n", - " \"clippy::{}\",\n", - " \"{}\",\n", - " );\n" - ), - lint.name, lint.reason, - ); - } - output.push_str("}\n"); - - output -} - /// Generates the code for registering lints #[must_use] fn gen_declared_lints<'a>( @@ -680,7 +620,7 @@ fn gen_declared_lints<'a>( fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { let mut res: String = GENERATED_FILE_COMMENT.into(); for lint in lints { - writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); + writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); } res.push_str("\nfn main() {}\n"); res @@ -699,27 +639,13 @@ fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String { seen_lints.clear(); for lint in lints { if seen_lints.insert(&lint.old_name) { - writeln!(res, "#![warn({})]", lint.old_name).unwrap(); + writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); } } res.push_str("\nfn main() {}\n"); res } -fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String { - const HEADER: &str = "\ - // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\ - #[rustfmt::skip]\n\ - pub static RENAMED_LINTS: &[(&str, &str)] = &[\n"; - - let mut res = String::from(HEADER); - for lint in lints { - writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap(); - } - res.push_str("];\n"); - res -} - /// Gathers all lints defined in `clippy_lints/src` fn gather_all() -> (Vec, Vec, Vec) { let mut lints = Vec::with_capacity(1000); @@ -744,10 +670,10 @@ fn gather_all() -> (Vec, Vec, Vec) { module.strip_suffix(".rs").unwrap_or(&module) }; - match module { - "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), - "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), - _ => parse_contents(&contents, module, &mut lints), + if module == "deprecated_lints" { + parse_deprecated_contents(&contents, &mut deprecated_lints, &mut renamed_lints); + } else { + parse_contents(&contents, module, &mut lints); } } (lints, deprecated_lints, renamed_lints) @@ -848,54 +774,37 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { } /// Parse a source file looking for `declare_deprecated_lint` macro invocations. -fn parse_deprecated_contents(contents: &str, lints: &mut Vec) { - let mut offset = 0usize; - let mut iter = tokenize(contents).map(|t| { - let range = offset..offset + t.len as usize; - offset = range.end; - - LintDeclSearchResult { - token_kind: t.kind, - content: &contents[range.clone()], - range, - } - }); +fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec, renamed: &mut Vec) { + let Some((_, contents)) = contents.split_once("\ndeclare_with_version! { DEPRECATED") else { + return; + }; + let Some((deprecated_src, renamed_src)) = contents.split_once("\ndeclare_with_version! { RENAMED") else { + return; + }; - while let Some(LintDeclSearchResult { range, .. }) = iter.find( - |LintDeclSearchResult { - token_kind, content, .. - }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", - ) { - let start = range.start; + for line in deprecated_src.lines() { + let mut offset = 0usize; + let mut iter = tokenize(line).map(|t| { + let range = offset..offset + t.len as usize; + offset = range.end; - let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| { - !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }) + LintDeclSearchResult { + token_kind: t.kind, + content: &line[range.clone()], + range, + } }); + let (name, reason) = match_tokens!( iter, - // !{ - Bang OpenBrace - // #[clippy::version = "version"] - Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket - // pub LINT_NAME, - Ident Ident(name) Comma - // "description" - Literal{kind: LiteralKind::Str{..},..}(reason) + // ("old_name", + Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(name) Comma + // "new_name"), + Whitespace Literal{kind: LiteralKind::Str{..},..}(reason) CloseParen Comma ); - - if let Some(LintDeclSearchResult { - token_kind: TokenKind::CloseBrace, - range, - .. - }) = iter.next() - { - lints.push(DeprecatedLint::new(name, reason, start..range.end)); - } + deprecated.push(DeprecatedLint::new(name, reason)); } -} - -fn parse_renamed_contents(contents: &str, lints: &mut Vec) { - for line in contents.lines() { + for line in renamed_src.lines() { let mut offset = 0usize; let mut iter = tokenize(line).map(|t| { let range = offset..offset + t.len as usize; @@ -915,7 +824,7 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec) { // "new_name"), Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma ); - lints.push(RenamedLint::new(old_name, new_name)); + renamed.push(RenamedLint::new(old_name, new_name)); } } @@ -1015,6 +924,12 @@ fn panic_file(error: io::Error, name: &Path, action: &str) -> ! { panic!("failed to {action} file `{}`: {error}", name.display()) } +fn insert_at_marker(text: &str, marker: &str, new_text: &str) -> Option { + let i = text.find(marker)?; + let (pre, post) = text.split_at(i); + Some([pre, new_text, post].into_iter().collect()) +} + fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option) { let mut file = OpenOptions::new() .write(true) @@ -1084,31 +999,6 @@ mod tests { assert_eq!(expected, result); } - #[test] - fn test_parse_deprecated_contents() { - static DEPRECATED_CONTENTS: &str = r#" - /// some doc comment - declare_deprecated_lint! { - #[clippy::version = "I'm a version"] - pub SHOULD_ASSERT_EQ, - "`assert!()` will be more flexible with RFC 2011" - } - "#; - - let mut result = Vec::new(); - parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![DeprecatedLint::new( - "should_assert_eq", - "\"`assert!()` will be more flexible with RFC 2011\"", - Range::default(), - )]; - assert_eq!(expected, result); - } - #[test] fn test_usable_lints() { let lints = vec![ @@ -1177,34 +1067,4 @@ mod tests { ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } - - #[test] - fn test_gen_deprecated() { - let lints = vec![ - DeprecatedLint::new( - "should_assert_eq", - "\"has been superseded by should_assert_eq2\"", - Range::default(), - ), - DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()), - ]; - - let expected = GENERATED_FILE_COMMENT.to_string() - + &[ - "{", - " store.register_removed(", - " \"clippy::should_assert_eq\",", - " \"has been superseded by should_assert_eq2\",", - " );", - " store.register_removed(", - " \"clippy::another_deprecated\",", - " \"will be removed\",", - " );", - "}", - ] - .join("\n") - + "\n"; - - assert_eq!(expected, gen_deprecated(&lints)); - } } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index eb04c006f89fd..99ed93468a01a 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -32,7 +32,6 @@ url = "2.2" walkdir = "2.3" [features] -deny-warnings = ["clippy_config/deny-warnings", "clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default internal = ["serde_json", "tempfile", "regex"] diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index e6d52bcef717c..3b4cc1134802d 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -97,7 +97,7 @@ impl ApproxConstant { cx, APPROX_CONSTANT, e.span, - format!("approximate value of `{module}::consts::{}` found", &name), + format!("approximate value of `{module}::consts::{name}` found"), None, "consider using the constant directly", ); diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index cfa25005a05ed..fefd8195f8e7e 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -52,13 +52,15 @@ impl<'tcx> LateLintPass<'tcx> for AsConversions { && !in_external_macro(cx.sess(), expr.span) && !is_from_proc_macro(cx, expr) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, AS_CONVERSIONS, expr.span, "using a potentially dangerous silent `as` conversion", - None, - "consider using a safe wrapper for this conversion", + |diag| { + diag.help("consider using a safe wrapper for this conversion"); + }, ); } } diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 0db1456d40bfc..69a8eb7d94e74 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -1,6 +1,6 @@ use std::fmt; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; use rustc_ast::{InlineAsm, Item, ItemKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; @@ -49,14 +49,10 @@ fn check_asm_syntax( }; if style == check_for { - span_lint_and_help( - cx, - lint, - span, - format!("{style} x86 assembly syntax used"), - None, - format!("use {} x86 assembly syntax", !style), - ); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, lint, span, format!("{style} x86 assembly syntax used"), |diag| { + diag.help(format!("use {} x86 assembly syntax", !style)); + }); } } } @@ -98,13 +94,13 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); impl EarlyLintPass for InlineAsmX86IntelSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel); + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); } } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel); + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); } } } @@ -146,13 +142,13 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att); + check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Att); } } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att); + check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, item.span, AsmStyle::Att); } } } diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index ed4cdce8cb886..7eaac80f969f7 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_inside_always_const_context; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; @@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return; }; - let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { + let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else { return; }; diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index 7217686dcca5b..f1cb4a05af86c 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; @@ -68,39 +68,28 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { return; } } - let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; - let mut app = Applicability::MachineApplicable; - match method_segment.ident.as_str() { + let (message, replacement) = match method_segment.ident.as_str() { "is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => { - span_lint_and_sugg( - cx, - ASSERTIONS_ON_RESULT_STATES, - macro_call.span, - "called `assert!` with `Result::is_ok`", - "replace with", - format!( - "{}.unwrap(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + ("called `assert!` with `Result::is_ok`", "unwrap") }, "is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => { - span_lint_and_sugg( - cx, - ASSERTIONS_ON_RESULT_STATES, - macro_call.span, - "called `assert!` with `Result::is_err`", - "replace with", - format!( - "{}.unwrap_err(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + ("called `assert!` with `Result::is_err`", "unwrap_err") }, - _ => (), + _ => return, }; + span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| { + let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; + let mut app = Applicability::MachineApplicable; + diag.span_suggestion( + macro_call.span, + "replace with", + format!( + "{}.{replacement}(){semicolon}", + snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 + ), + app, + ); + }); } } } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 03f777600f084..6e336efbb90a2 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -227,9 +227,22 @@ fn build_sugg<'tcx>( match call_kind { CallKind::Method => { let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", app) + // If `ref_expr` is a reference, we can remove the dereference operator (`*`) to make + // the generated code a bit simpler. In other cases, we don't do this special case, to avoid + // having to deal with Deref (https://github.com/rust-lang/rust-clippy/issues/12437). + + let ty = cx.typeck_results().expr_ty(ref_expr); + if ty.is_ref() { + // Apply special case, remove `*` + // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) + } else { + // Keep the original lhs + // `*lhs = self_expr.clone();` -> `(*lhs).clone_from(self_expr)` + Sugg::hir_with_applicability(cx, lhs, "_", app) + } } else { + // Keep the original lhs // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` Sugg::hir_with_applicability(cx, lhs, "_", app) } @@ -249,8 +262,16 @@ fn build_sugg<'tcx>( }, CallKind::Ufcs => { let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", app) + // See special case of removing `*` in method handling above + let ty = cx.typeck_results().expr_ty(ref_expr); + if ty.is_ref() { + // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) + } else { + // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut *lhs, self_expr)` + // mut_addr_deref is used to avoid unnecessary parentheses around `*lhs` + Sugg::hir_with_applicability(cx, ref_expr, "_", app).mut_addr_deref() + } } else { // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)` Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr() diff --git a/clippy_lints/src/attrs/allow_attributes.rs b/clippy_lints/src/attrs/allow_attributes.rs index df9994086cd48..a5a7b9f74a693 100644 --- a/clippy_lints/src/attrs/allow_attributes.rs +++ b/clippy_lints/src/attrs/allow_attributes.rs @@ -1,5 +1,5 @@ use super::ALLOW_ATTRIBUTES; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_ast::{AttrStyle, Attribute}; use rustc_errors::Applicability; @@ -13,14 +13,14 @@ pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) { && let Some(ident) = attr.ident() && !is_from_proc_macro(cx, attr) { - span_lint_and_sugg( - cx, - ALLOW_ATTRIBUTES, - ident.span, - "#[allow] attribute found", - "replace it with", - "expect".into(), - Applicability::MachineApplicable, - ); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, ALLOW_ATTRIBUTES, ident.span, "#[allow] attribute found", |diag| { + diag.span_suggestion( + ident.span, + "replace it with", + "expect", + Applicability::MachineApplicable, + ); + }); } } diff --git a/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/clippy_lints/src/attrs/allow_attributes_without_reason.rs index 4b42616a636bc..4ab97118df1d4 100644 --- a/clippy_lints/src/attrs/allow_attributes_without_reason.rs +++ b/clippy_lints/src/attrs/allow_attributes_without_reason.rs @@ -1,5 +1,5 @@ use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON}; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_ast::{MetaItemKind, NestedMetaItem}; use rustc_lint::{LateContext, LintContext}; @@ -21,12 +21,14 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet return; } - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, ALLOW_ATTRIBUTES_WITHOUT_REASON, attr.span, format!("`{}` attribute without specifying a reason", name.as_str()), - None, - "try adding a reason at the end with `, reason = \"..\"`", + |diag| { + diag.help("try adding a reason at the end with `, reason = \"..\"`"); + }, ); } diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 561ca9bd9866d..612712d16843c 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::Sugg; -use clippy_utils::{in_constant, is_else_clause}; +use clippy_utils::{is_else_clause, is_in_const_context}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { && let Some(else_lit) = as_int_bool_lit(else_) && then_lit != else_lit && !expr.span.from_expansion() - && !in_constant(cx, expr.hir_id) + && !is_in_const_context(cx) { let ty = cx.typeck_results().expr_ty(then); let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index a1c6c0b608f72..a2f48c18170a6 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -477,14 +477,12 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { cx: self.cx, }; if let Ok(expr) = h2q.run(e) { - if h2q.terminals.len() > 8 { - // QMC has exponentially slow behavior as the number of terminals increases - // 8 is reasonable, it takes approximately 0.2 seconds. - // See #825 + let stats = terminal_stats(&expr); + if stats.ops > 7 { + // QMC has exponentially slow behavior as the number of ops increases. + // See #825, #13206 return; } - - let stats = terminal_stats(&expr); let mut simplified = expr.simplify(); for simple in Bool::Not(Box::new(expr)).simplify() { match simple { diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index ff460a3fd8e39..bd3acc06f4b2b 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::in_constant; +use clippy_utils::is_in_const_context; use clippy_utils::source::snippet_opt; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_isize_or_usize; @@ -21,7 +21,7 @@ pub(super) fn check( cast_to_hir: &rustc_hir::Ty<'_>, msrv: &Msrv, ) { - if !should_lint(cx, expr, cast_from, cast_to, msrv) { + if !should_lint(cx, cast_from, cast_to, msrv) { return; } @@ -70,9 +70,9 @@ pub(super) fn check( ); } -fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { +fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { // Do not suggest using From in consts/statics until it is valid to do so (see #2267). - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return false; } diff --git a/clippy_lints/src/casts/cast_nan_to_int.rs b/clippy_lints/src/casts/cast_nan_to_int.rs index 5bc8692c289f7..464eabe5d9ab7 100644 --- a/clippy_lints/src/casts/cast_nan_to_int.rs +++ b/clippy_lints/src/casts/cast_nan_to_int.rs @@ -1,6 +1,6 @@ use super::CAST_NAN_TO_INT; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_note; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, } fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - match constant(cx, cx.typeck_results(), e) { + match ConstEvalCtxt::new(cx).eval(e) { // FIXME(f16_f128): add these types when nan checks are available on all platforms Some(Constant::F64(n)) => n.is_nan(), Some(Constant::F32(n)) => n.is_nan(), diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 7c5acd1a678d7..102fe25fc67b0 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::expr_or_init; use clippy_utils::source::snippet; @@ -15,7 +15,7 @@ use rustc_target::abi::IntegerType; use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(Constant::Int(c)) = constant(cx, cx.typeck_results(), expr) { + if let Some(Constant::Int(c)) = ConstEvalCtxt::new(cx).eval(expr) { Some(c) } else { None diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 8bbd41b0db1ee..9daf237344a4b 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use std::ops::ControlFlow; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::visitors::{for_each_expr_without_closures, Descend}; use clippy_utils::{method_chain_args, sext}; @@ -88,7 +88,7 @@ fn get_const_signed_int_eval<'cx>( ) -> Option { let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Int(ity) = *ty.kind() { return Some(sext(cx.tcx, n, ity)); @@ -103,7 +103,7 @@ fn get_const_unsigned_int_eval<'cx>( ) -> Option { let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Uint(_ity) = *ty.kind() { return Some(n); diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs index 826589bf303b5..75de53f73ee7a 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; @@ -14,21 +14,24 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, _ => { /* continue to checks */ }, } - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(_) => { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); + if let ty::FnDef(..) | ty::FnPtr(_) = cast_from.kind() { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST_ANY, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`"), - "did you mean to invoke the function?", - format!("{from_snippet}() as {cast_to}"), - applicability, - ); - }, - _ => {}, + span_lint_and_then( + cx, + FN_TO_NUMERIC_CAST_ANY, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`"), + |diag| { + diag.span_suggestion_with_style( + expr.span, + "did you mean to invoke the function?", + format!("{from_snippet}() as {cast_to}"), + applicability, + SuggestionStyle::ShowAlways, + ); + }, + ); } } diff --git a/clippy_lints/src/casts/zero_ptr.rs b/clippy_lints/src/casts/zero_ptr.rs index 5071af5ecb986..3c1c7d2dc3a54 100644 --- a/clippy_lints/src/casts/zero_ptr.rs +++ b/clippy_lints/src/casts/zero_ptr.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::{in_constant, is_integer_literal, std_or_core}; +use clippy_utils::{is_in_const_context, is_integer_literal, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -10,7 +10,7 @@ use super::ZERO_PTR; pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) { if let TyKind::Ptr(ref mut_ty) = to.kind && is_integer_literal(from, 0) - && !in_constant(cx, from.hir_id) + && !is_in_const_context(cx) && let Some(std_or_core) = std_or_core(cx) { let (msg, sugg_fn) = match mut_ty.mutbl { diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 0b1ab5411bf18..1711565fca891 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -4,7 +4,7 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{in_constant, is_integer_literal, SpanlessEq}; +use clippy_utils::{is_in_const_context, is_integer_literal, SpanlessEq}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions { _ => return, } && !in_external_macro(cx.sess(), item.span) - && !in_constant(cx, item.hir_id) + && !is_in_const_context(cx) && self.msrv.meets(msrvs::TRY_FROM) && let Some(cv) = match op2 { // todo: check for case signed -> larger unsigned == only x >= 0 diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 2c23c0b4f154c..5d78744e9b5e2 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::implements_trait; -use clippy_utils::{if_sequence, in_constant, is_else_clause, SpanlessEq}; +use clippy_utils::{if_sequence, is_else_clause, is_in_const_context, SpanlessEq}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { return; } - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return; } diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 27c00948a8f2b..b49a977dbeaf1 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use rustc_errors::Applicability; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -39,14 +39,24 @@ impl LateLintPass<'_> for CreateDir { && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id) { - span_lint_and_sugg( + span_lint_and_then( cx, CREATE_DIR, expr.span, "calling `std::fs::create_dir` where there may be a better way", - "consider calling `std::fs::create_dir_all` instead", - format!("create_dir_all({})", snippet(cx, arg.span, "..")), - Applicability::MaybeIncorrect, + |diag| { + let mut app = Applicability::MaybeIncorrect; + diag.span_suggestion_with_style( + expr.span, + "consider calling `std::fs::create_dir_all` instead", + format!( + "create_dir_all({})", + snippet_with_applicability(cx, arg.span, "..", &mut app) + ), + app, + SuggestionStyle::ShowAlways, + ); + }, ); } } diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 788c6f3ada297..93c8fff05e9ed 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_in_test; use clippy_utils::macros::{macro_backtrace, MacroCall}; use clippy_utils::source::snippet_with_applicability; @@ -65,61 +65,67 @@ impl LateLintPass<'_> for DbgMacro { // allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml !(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id)) { - let mut applicability = Applicability::MachineApplicable; - - let (sugg_span, suggestion) = match expr.peel_drop_temps().kind { - // dbg!() - ExprKind::Block(..) => { - // If the `dbg!` macro is a "free" statement and not contained within other expressions, - // remove the whole statement. - if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) - && let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) - { - (macro_call.span.to(semi_span), String::new()) - } else { - (macro_call.span, String::from("()")) - } - }, - // dbg!(1) - ExprKind::Match(val, ..) => ( - macro_call.span, - snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string(), - ), - // dbg!(2, 3) - ExprKind::Tup( - [ - Expr { - kind: ExprKind::Match(first, ..), - .. - }, - .., - Expr { - kind: ExprKind::Match(last, ..), - .. - }, - ], - ) => { - let snippet = snippet_with_applicability( - cx, - first.span.source_callsite().to(last.span.source_callsite()), - "..", - &mut applicability, - ); - (macro_call.span, format!("({snippet})")) - }, - _ => return, - }; - self.prev_ctxt = cur_syntax_ctxt; - span_lint_and_sugg( + span_lint_and_then( cx, DBG_MACRO, - sugg_span, + macro_call.span, "the `dbg!` macro is intended as a debugging tool", - "remove the invocation before committing it to a version control system", - suggestion, - applicability, + |diag| { + let mut applicability = Applicability::MachineApplicable; + + let (sugg_span, suggestion) = match expr.peel_drop_temps().kind { + // dbg!() + ExprKind::Block(..) => { + // If the `dbg!` macro is a "free" statement and not contained within other expressions, + // remove the whole statement. + if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) + && let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) + { + (macro_call.span.to(semi_span), String::new()) + } else { + (macro_call.span, String::from("()")) + } + }, + // dbg!(1) + ExprKind::Match(val, ..) => ( + macro_call.span, + snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) + .to_string(), + ), + // dbg!(2, 3) + ExprKind::Tup( + [ + Expr { + kind: ExprKind::Match(first, ..), + .. + }, + .., + Expr { + kind: ExprKind::Match(last, ..), + .. + }, + ], + ) => { + let snippet = snippet_with_applicability( + cx, + first.span.source_callsite().to(last.span.source_callsite()), + "..", + &mut applicability, + ); + (macro_call.span, format!("({snippet})")) + }, + _ => unreachable!(), + }; + + diag.span_suggestion( + sugg_span, + "remove the invocation before committing it to a version control system", + suggestion, + applicability, + ); + }, ); } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 69f9eb6842bcd..3fb083dd83310 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -14,8 +14,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO, #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON_INFO, - #[cfg(feature = "internal")] crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO, @@ -741,6 +739,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unused_async::UNUSED_ASYNC_INFO, crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO, crate::unused_peekable::UNUSED_PEEKABLE_INFO, + crate::unused_result_ok::UNUSED_RESULT_OK_INFO, crate::unused_rounding::UNUSED_ROUNDING_INFO, crate::unused_self::UNUSED_SELF_INFO, crate::unused_unit::UNUSED_UNIT_INFO, diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 72fa05be3cc60..0b7279f2b360d 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -221,7 +221,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { .map(ToString::to_string) .collect::>() .join(", "); - format!("{adt_def_ty_name}::<{}>", &tys_str) + format!("{adt_def_ty_name}::<{tys_str}>") } else { binding_type.to_string() }; diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 9af73db6849f4..a74b3a8c8362c 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -92,20 +92,8 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { let (suffix, is_float) = match lit_ty.kind() { ty::Int(IntTy::I32) => ("i32", false), ty::Float(FloatTy::F64) => ("f64", true), - // Default numeric fallback never results in other types. _ => return, }; - - let src = if let Some(src) = snippet_opt(self.cx, lit.span) { - src - } else { - match lit.node { - LitKind::Int(src, _) => format!("{src}"), - LitKind::Float(src, _) => format!("{src}"), - _ => return, - } - }; - let sugg = numeric_literal::format(&src, Some(suffix), is_float); span_lint_hir_and_then( self.cx, DEFAULT_NUMERIC_FALLBACK, @@ -113,6 +101,17 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { lit.span, "default numeric fallback might occur", |diag| { + let src = if let Some(src) = snippet_opt(self.cx, lit.span) { + src + } else { + match lit.node { + LitKind::Int(src, _) => format!("{src}"), + LitKind::Float(src, _) => format!("{src}"), + _ => unreachable!("Default numeric fallback never results in other types"), + } + }; + + let sugg = numeric_literal::format(&src, Some(suffix), is_float); diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect); }, ); diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 3fa9bad0d03da..9f020d3081c40 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_hir::{HirId, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; @@ -56,16 +56,18 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { && is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, DEFAULT_UNION_REPRESENTATION, item.span, "this union has the default representation", - None, - format!( - "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", - cx.tcx.def_path_str(item.owner_id) - ), + |diag| { + diag.help(format!( + "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", + cx.tcx.def_path_str(item.owner_id) + )); + }, ); } } diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index a0900f46f6aa6..0066ed643251a 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -1,243 +1,181 @@ -// NOTE: Entries should be created with `cargo dev deprecate` - -/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This -/// enables the simple extraction of the metadata without changing the current deprecation -/// declaration. -pub struct ClippyDeprecatedLint { - #[allow(dead_code)] - pub desc: &'static str, -} - -#[macro_export] -macro_rules! declare_deprecated_lint { - { $(#[$attr:meta])* pub $name: ident, $reason: literal} => { - $(#[$attr])* - #[allow(dead_code)] - pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint { - desc: $reason - }; - } -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `assert!(a == b)` and recommend - /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011. +// This file is managed by `cargo dev rename_lint` and `cargo dev deprecate_lint`. +// Prefer to use those when possible. + +macro_rules! declare_with_version { + ($name:ident($name_version:ident): &[$ty:ty] = &[$( + #[clippy::version = $version:literal] + $e:expr, + )*]) => { + pub static $name: &[$ty] = &[$($e),*]; + #[allow(unused)] + pub static $name_version: &[&str] = &[$($version),*]; + }; +} + +#[rustfmt::skip] +declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[ #[clippy::version = "pre 1.29.0"] - pub SHOULD_ASSERT_EQ, - "`assert!()` will be more flexible with RFC 2011" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::extend`, which was slower than - /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true. + ("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"), #[clippy::version = "pre 1.29.0"] - pub EXTEND_FROM_SLICE, - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// `Range::step_by(0)` used to be linted since it's - /// an infinite iterator, which is better expressed by `iter::repeat`, - /// but the method has been removed for `Iterator::step_by` which panics - /// if given a zero + ("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"), #[clippy::version = "pre 1.29.0"] - pub RANGE_STEP_BY_ZERO, - "`iterator.step_by(0)` panics nowadays" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::as_slice`, which was unstable with good - /// stable alternatives. `Vec::as_slice` has now been stabilized. + ("clippy::range_step_by_zero", "`Iterator::step_by(0)` now panics and is no longer an infinite iterator"), #[clippy::version = "pre 1.29.0"] - pub UNSTABLE_AS_SLICE, - "`Vec::as_slice` has been stabilized in 1.7" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::as_mut_slice`, which was unstable with good - /// stable alternatives. `Vec::as_mut_slice` has now been stabilized. + ("clippy::unstable_as_slice", "`Vec::as_slice` is now stable"), #[clippy::version = "pre 1.29.0"] - pub UNSTABLE_AS_MUT_SLICE, - "`Vec::as_mut_slice` has been stabilized in 1.7" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint should never have applied to non-pointer types, as transmuting - /// between non-pointer types of differing alignment is well-defined behavior (it's semantically - /// equivalent to a memcpy). This lint has thus been refactored into two separate lints: - /// cast_ptr_alignment and transmute_ptr_to_ptr. + ("clippy::unstable_as_mut_slice", "`Vec::as_mut_slice` is now stable"), #[clippy::version = "pre 1.29.0"] - pub MISALIGNED_TRANSMUTE, - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint is too subjective, not having a good reason for being in clippy. - /// Additionally, compound assignment operators may be overloaded separately from their non-assigning - /// counterparts, so this lint may suggest a change in behavior or the code may not compile. + ("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"), #[clippy::version = "1.30.0"] - pub ASSIGN_OPS, - "using compound assignment operators (e.g., `+=`) is harmless" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The original rule will only lint for `if let`. After - /// making it support to lint `match`, naming as `if let` is not suitable for it. - /// So, this lint is deprecated. + ("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"), #[clippy::version = "pre 1.29.0"] - pub IF_LET_REDUNDANT_PATTERN_MATCHING, - "this lint has been changed to redundant_pattern_matching" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint used to suggest replacing `let mut vec = - /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The - /// replacement has very different performance characteristics so the lint is - /// deprecated. - #[clippy::version = "pre 1.29.0"] - pub UNSAFE_VECTOR_INITIALIZATION, - "the replacement suggested by this lint had substantially different behavior" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by #[must_use] in rustc. + ("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"), #[clippy::version = "1.39.0"] - pub UNUSED_COLLECT, - "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// Associated-constants are now preferred. + ("clippy::unused_collect", "`Iterator::collect` is now marked as `#[must_use]`"), #[clippy::version = "1.44.0"] - pub REPLACE_CONSTS, - "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The regex! macro does not exist anymore. + ("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"), #[clippy::version = "1.47.0"] - pub REGEX_MACRO, - "the regex! macro has been removed from the regex crate in 2018" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been replaced by `manual_find_map`, a - /// more specific lint. - #[clippy::version = "1.51.0"] - pub FIND_MAP, - "this lint has been replaced by `manual_find_map`, a more specific lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been replaced by `manual_filter_map`, a - /// more specific lint. - #[clippy::version = "1.53.0"] - pub FILTER_MAP, - "this lint has been replaced by `manual_filter_map`, a more specific lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The `avoid_breaking_exported_api` config option was added, which - /// enables the `enum_variant_names` lint for public items. + ("clippy::regex_macro", "the `regex!` macro was removed from the regex crate in 2018"), #[clippy::version = "1.54.0"] - pub PUB_ENUM_VARIANT_NAMES, - "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The `avoid_breaking_exported_api` config option was added, which - /// enables the `wrong_self_conversion` lint for public items. + ("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"), #[clippy::version = "1.54.0"] - pub WRONG_PUB_SELF_CONVENTION, - "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect the `#[cfg(features)]` and `#[cfg(tests)]` typos. - /// - /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs + ("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"), + // end deprecated lints. used by `cargo dev deprecate_lint` +]} + +#[rustfmt::skip] +declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[ + #[clippy::version = ""] + ("clippy::almost_complete_letter_range", "clippy::almost_complete_range"), + #[clippy::version = ""] + ("clippy::blacklisted_name", "clippy::disallowed_names"), + #[clippy::version = ""] + ("clippy::block_in_if_condition_expr", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::block_in_if_condition_stmt", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::blocks_in_if_conditions", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::box_vec", "clippy::box_collection"), + #[clippy::version = ""] + ("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"), + #[clippy::version = ""] + ("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"), + #[clippy::version = ""] + ("clippy::derive_hash_xor_eq", "clippy::derived_hash_with_manual_eq"), + #[clippy::version = ""] + ("clippy::disallowed_method", "clippy::disallowed_methods"), + #[clippy::version = ""] + ("clippy::disallowed_type", "clippy::disallowed_types"), + #[clippy::version = ""] + ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"), + #[clippy::version = "1.51.0"] + ("clippy::find_map", "clippy::manual_find_map"), + #[clippy::version = "1.53.0"] + ("clippy::filter_map", "clippy::manual_filter_map"), + #[clippy::version = ""] + ("clippy::identity_conversion", "clippy::useless_conversion"), + #[clippy::version = "pre 1.29.0"] + ("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"), + #[clippy::version = ""] + ("clippy::if_let_some_result", "clippy::match_result_ok"), + #[clippy::version = ""] + ("clippy::incorrect_clone_impl_on_copy_type", "clippy::non_canonical_clone_impl"), + #[clippy::version = ""] + ("clippy::incorrect_partial_ord_impl_on_ord_type", "clippy::non_canonical_partial_ord_impl"), + #[clippy::version = ""] + ("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"), + #[clippy::version = ""] + ("clippy::logic_bug", "clippy::overly_complex_bool_expr"), + #[clippy::version = ""] + ("clippy::new_without_default_derive", "clippy::new_without_default"), + #[clippy::version = ""] + ("clippy::option_and_then_some", "clippy::bind_instead_of_map"), + #[clippy::version = ""] + ("clippy::option_expect_used", "clippy::expect_used"), + #[clippy::version = ""] + ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::option_unwrap_used", "clippy::unwrap_used"), + #[clippy::version = ""] + ("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"), + #[clippy::version = ""] + ("clippy::ref_in_deref", "clippy::needless_borrow"), + #[clippy::version = ""] + ("clippy::result_expect_used", "clippy::expect_used"), + #[clippy::version = ""] + ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::result_unwrap_used", "clippy::unwrap_used"), + #[clippy::version = ""] + ("clippy::single_char_push_str", "clippy::single_char_add_str"), + #[clippy::version = ""] + ("clippy::stutter", "clippy::module_name_repetitions"), + #[clippy::version = ""] + ("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"), + #[clippy::version = ""] + ("clippy::to_string_in_display", "clippy::recursive_format_impl"), + #[clippy::version = ""] + ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), + #[clippy::version = ""] + ("clippy::zero_width_space", "clippy::invisible_characters"), + #[clippy::version = ""] + ("clippy::cast_ref_to_mut", "invalid_reference_casting"), + #[clippy::version = ""] + ("clippy::clone_double_ref", "suspicious_double_ref_op"), + #[clippy::version = ""] + ("clippy::cmp_nan", "invalid_nan_comparisons"), + #[clippy::version = ""] + ("clippy::drop_bounds", "drop_bounds"), + #[clippy::version = ""] + ("clippy::drop_copy", "dropping_copy_types"), + #[clippy::version = ""] + ("clippy::drop_ref", "dropping_references"), + #[clippy::version = ""] + ("clippy::fn_null_check", "useless_ptr_null_checks"), + #[clippy::version = ""] + ("clippy::for_loop_over_option", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::for_loop_over_result", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::forget_copy", "forgetting_copy_types"), + #[clippy::version = ""] + ("clippy::forget_ref", "forgetting_references"), + #[clippy::version = ""] + ("clippy::into_iter_on_array", "array_into_iter"), + #[clippy::version = ""] + ("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"), + #[clippy::version = ""] + ("clippy::invalid_ref", "invalid_value"), + #[clippy::version = ""] + ("clippy::invalid_utf8_in_unchecked", "invalid_from_utf8_unchecked"), + #[clippy::version = ""] + ("clippy::let_underscore_drop", "let_underscore_drop"), #[clippy::version = "1.80.0"] - pub MAYBE_MISUSED_CFG, - "this lint has been replaced by `unexpected_cfgs`" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect invalid `#[cfg(linux)]` attributes. - /// - /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs + ("clippy::maybe_misused_cfg", "unexpected_cfgs"), + #[clippy::version = ""] + ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"), #[clippy::version = "1.80.0"] - pub MISMATCHED_TARGET_OS, - "this lint has been replaced by `unexpected_cfgs`" -} + ("clippy::mismatched_target_os", "unexpected_cfgs"), + #[clippy::version = ""] + ("clippy::panic_params", "non_fmt_panics"), + #[clippy::version = ""] + ("clippy::positional_named_format_parameters", "named_arguments_used_positionally"), + #[clippy::version = ""] + ("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"), + #[clippy::version = ""] + ("clippy::undropped_manually_drops", "undropped_manually_drops"), + #[clippy::version = ""] + ("clippy::unknown_clippy_lints", "unknown_lints"), + #[clippy::version = ""] + ("clippy::unused_label", "unused_labels"), + #[clippy::version = ""] + ("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"), + #[clippy::version = ""] + ("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"), + // end renamed lints. used by `cargo dev rename_lint` +]} diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 253f9959e13e7..d0cb24884686a 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; +use clippy_utils::ty::{implements_trait, is_manually_drop}; use clippy_utils::{ expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy, ExprUseNode, @@ -947,7 +947,7 @@ fn report<'tcx>( let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); let ty = typeck.expr_ty(expr); - let (_, ref_count) = peel_mid_ty_refs(ty); + let (_, ref_count) = peel_middle_ty_refs(ty); let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { // a deref call changing &T -> &U requires two deref operators the first time // this occurs. One to remove the reference, a second to call the deref impl. diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5b6a5b08aa94c..d7b3a7c74f3ce 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::Visitable; -use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; +use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use pulldown_cmark::Event::{ Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, TaskListMarker, Text, @@ -768,7 +768,7 @@ fn check_doc<'a, Events: Iterator, Range { + Text(text) => { paragraph_range.end = range.end; let range_ = range.clone(); ticks_unbalanced |= text.contains('`') @@ -812,7 +812,8 @@ fn check_doc<'a, Events: Iterator, Range {} } } headers @@ -857,7 +858,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { "assert" | "assert_eq" | "assert_ne" ) { - self.is_const = in_constant(self.cx, expr.hir_id); + self.is_const = self.cx.tcx.hir().is_inside_const_context(expr.hir_id); self.panic_span = Some(macro_call.span); } } diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 4a6ffcd9a7888..c7dd7292a14be 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_must_use_func_call; use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; @@ -126,14 +126,14 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { }, _ => return, }; - span_lint_and_note( - cx, - lint, - expr.span, - msg, - note_span, - format!("argument has type `{arg_ty}`"), - ); + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let note = format!("argument has type `{arg_ty}`"); + if let Some(span) = note_span { + diag.span_note(span, note); + } else { + diag.note(note); + } + }); } } } diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index 7a6dc46972767..02f9c2c36488d 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -1,6 +1,6 @@ //! Lint on if expressions with an else if, but without a final else branch. -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Expr, ExprKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -54,13 +54,15 @@ impl EarlyLintPass for ElseIfWithoutElse { && let ExprKind::If(_, _, None) = els.kind && !in_external_macro(cx.sess(), item.span) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, ELSE_IF_WITHOUT_ELSE, els.span, "`if` expression with an `else if`, but without a final `else`", - None, - "add an `else` block here", + |diag| { + diag.help("add an `else` block here"); + }, ); } } diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index c5fc72b5e2d8e..b66dd2108fc9b 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::peel_blocks; use rustc_errors::Applicability; use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node}; @@ -50,15 +50,14 @@ impl LateLintPass<'_> for EmptyDrop { && block.stmts.is_empty() && block.expr.is_none() { - span_lint_and_sugg( - cx, - EMPTY_DROP, - item.span, - "empty drop implementation", - "try removing this impl", - String::new(), - Applicability::MaybeIncorrect, - ); + span_lint_and_then(cx, EMPTY_DROP, item.span, "empty drop implementation", |diag| { + diag.span_suggestion_hidden( + item.span, + "try removing this impl", + String::new(), + Applicability::MaybeIncorrect, + ); + }); } } } diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index 5bba9c562b931..209104c5385c9 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -7,7 +7,6 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::Ty; use rustc_session::declare_lint_pass; use rustc_span::Symbol; -use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -141,52 +140,6 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix _ => return, }; - let mut help = None; - - 'build_help: { - // all lints disallowed, don't give help here - if [&[lint], other_lints.as_slice()] - .concat() - .iter() - .all(|lint| !lint.allowed(cx, expr)) - { - break 'build_help; - } - - // ne_bytes and all other lints allowed - if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) { - help = Some(Cow::Borrowed("specify the desired endianness explicitly")); - break 'build_help; - } - - // le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but - // le_bytes is not - if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) { - help = Some(Cow::Borrowed("use the native endianness instead")); - break 'build_help; - } - - let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr)); - let len = allowed_lints.clone().count(); - - let mut help_str = "use ".to_owned(); - - for (i, lint) in allowed_lints.enumerate() { - let only_one = len == 1; - if !only_one { - help_str.push_str("either of "); - } - - help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); - - if i != len && !only_one { - help_str.push_str("or "); - } - } - - help = Some(Cow::Owned(help_str + "instead")); - } - span_lint_and_then( cx, lint.as_lint(), @@ -198,9 +151,47 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix if prefix == Prefix::To { " method" } else { "" }, ), move |diag| { - if let Some(help) = help { - diag.help(help); + // all lints disallowed, don't give help here + if [&[lint], other_lints.as_slice()] + .concat() + .iter() + .all(|lint| !lint.allowed(cx, expr)) + { + return; + } + + // ne_bytes and all other lints allowed + if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) { + diag.help("specify the desired endianness explicitly"); + return; + } + + // le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but + // le_bytes is not + if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) { + diag.help("use the native endianness instead"); + return; + } + + let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr)); + let len = allowed_lints.clone().count(); + + let mut help_str = "use ".to_owned(); + + for (i, lint) in allowed_lints.enumerate() { + let only_one = len == 1; + if !only_one { + help_str.push_str("either of "); + } + + help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); + + if i != len && !only_one { + help_str.push_str("or "); + } } + help_str.push_str("instead"); + diag.help(help_str); }, ); } diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 30eb643c42ece..e54cd248ead29 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { .const_eval_poly(def_id.to_def_id()) .ok() .map(|val| rustc_middle::mir::Const::from_value(val, ty)); - if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx, c)) { + if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) { if let ty::Adt(adt, _) = ty.kind() { if adt.is_enum() { ty = adt.repr().discr_type().to_ty(cx.tcx); diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 0ed7859418bc5..5a7226d590c4d 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, TyCtxt, - TypeVisitableExt, TypeckResults, + self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -74,159 +73,184 @@ declare_clippy_lint! { declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); impl<'tcx> LateLintPass<'tcx> for EtaReduction { - #[allow(clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let body = if let ExprKind::Closure(c) = expr.kind - && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) - && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) - && !expr.span.from_expansion() - { - cx.tcx.hir().body(c.body) - } else { - return; - }; - - if body.value.span.from_expansion() { - if body.params.is_empty() { - if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { - // replace `|| vec![]` with `Vec::new` - span_lint_and_sugg( - cx, - REDUNDANT_CLOSURE, - expr.span, - "redundant closure", - "replace the closure with `Vec::new`", - "std::vec::Vec::new".into(), - Applicability::MachineApplicable, - ); - } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::MethodCall(_method, receiver, args, _) = expr.kind { + for arg in args { + check_clousure(cx, Some(receiver), arg); } - // skip `foo(|| macro!())` - return; } + if let ExprKind::Call(func, args) = expr.kind { + check_clousure(cx, None, func); + for arg in args { + check_clousure(cx, None, arg); + } + } + } +} - let typeck = cx.typeck_results(); - let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { - closure_subs.as_closure() - } else { - return; - }; +#[allow(clippy::too_many_lines)] +fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx>>, expr: &Expr<'tcx>) { + let body = if let ExprKind::Closure(c) = expr.kind + && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) + && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) + && !expr.span.from_expansion() + { + cx.tcx.hir().body(c.body) + } else { + return; + }; - if is_adjusted(cx, body.value) { - return; + if body.value.span.from_expansion() { + if body.params.is_empty() { + if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { + // replace `|| vec![]` with `Vec::new` + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE, + expr.span, + "redundant closure", + "replace the closure with `Vec::new`", + "std::vec::Vec::new".into(), + Applicability::MachineApplicable, + ); + } } + // skip `foo(|| macro!())` + return; + } - match body.value.kind { - ExprKind::Call(callee, args) - if matches!( - callee.kind, - ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) - ) => + if is_adjusted(cx, body.value) { + return; + } + + let typeck = cx.typeck_results(); + let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { + closure_subs.as_closure() + } else { + return; + }; + let closure_sig = cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(); + match body.value.kind { + ExprKind::Call(callee, args) + if matches!( + callee.kind, + ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) + ) => + { + let callee_ty_raw = typeck.expr_ty(callee); + let callee_ty = callee_ty_raw.peel_refs(); + if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) + || !check_inputs(typeck, body.params, None, args) { - let callee_ty_raw = typeck.expr_ty(callee); - let callee_ty = callee_ty_raw.peel_refs(); - if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) - || !check_inputs(typeck, body.params, None, args) - { - return; - } - let callee_ty_adjusted = typeck - .expr_adjustments(callee) - .last() - .map_or(callee_ty, |a| a.target.peel_refs()); + return; + } + let callee_ty_adjusted = typeck + .expr_adjustments(callee) + .last() + .map_or(callee_ty, |a| a.target.peel_refs()); - let sig = match callee_ty_adjusted.kind() { - ty::FnDef(def, _) => { - // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` - if cx.tcx.has_attr(*def, sym::track_caller) { - return; - } + let sig = match callee_ty_adjusted.kind() { + ty::FnDef(def, _) => { + // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` + if cx.tcx.has_attr(*def, sym::track_caller) { + return; + } - cx.tcx.fn_sig(def).skip_binder().skip_binder() - }, - ty::FnPtr(sig) => sig.skip_binder(), - ty::Closure(_, subs) => cx - .tcx - .signature_unclosure(subs.as_closure().sig(), Safety::Safe) - .skip_binder(), - _ => { - if typeck.type_dependent_def_id(body.value.hir_id).is_some() - && let subs = typeck.node_args(body.value.hir_id) - && let output = typeck.expr_ty(body.value) - && let ty::Tuple(tys) = *subs.type_at(1).kind() - { - cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) - } else { - return; - } - }, - }; - if check_sig(cx, closure, sig) - && let generic_args = typeck.node_args(callee.hir_id) - // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not - // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result - // in a type which is `'static`. - // For now ignore all callee types which reference a type parameter. - && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) - { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { - if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if path_to_local(callee).map_or(false, |l| { - // FIXME: Do we really need this `local_used_in` check? - // Isn't it checking something like... `callee(callee)`? - // If somehow this check is needed, add some test for it, - // 'cuz currently nothing changes after deleting this check. - local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) - }) { - match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { - // Mutable closure is used after current expr; we cannot consume it. - Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), - Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { - snippet = format!("&{snippet}"); - }, - _ => (), - } + cx.tcx.fn_sig(def).skip_binder().skip_binder() + }, + ty::FnPtr(sig) => sig.skip_binder(), + ty::Closure(_, subs) => cx + .tcx + .signature_unclosure(subs.as_closure().sig(), Safety::Safe) + .skip_binder(), + _ => { + if typeck.type_dependent_def_id(body.value.hir_id).is_some() + && let subs = typeck.node_args(body.value.hir_id) + && let output = typeck.expr_ty(body.value) + && let ty::Tuple(tys) = *subs.type_at(1).kind() + { + cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) + } else { + return; + } + }, + }; + if let Some(outer) = outer_receiver + && ty_has_static(sig.output()) + && let generic_args = typeck.node_args(outer.hir_id) + // HACK: Given a closure in `T.method(|| f())`, where `fn f() -> U where U: 'static`, `T.method(f)` + // will succeed iff `T: 'static`. But the region of `T` is always erased by `typeck.expr_ty()` when + // T is a generic type. For example, return type of `Option::as_deref()` is a generic. + // So we have a hack like this. + && generic_args.len() > 0 + { + return; + } + if check_sig(closure_sig, sig) + && let generic_args = typeck.node_args(callee.hir_id) + // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not + // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result + // in a type which is `'static`. + // For now ignore all callee types which reference a type parameter. + && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + { + span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { + if let Some(mut snippet) = snippet_opt(cx, callee.span) { + if path_to_local(callee).map_or(false, |l| { + // FIXME: Do we really need this `local_used_in` check? + // Isn't it checking something like... `callee(callee)`? + // If somehow this check is needed, add some test for it, + // 'cuz currently nothing changes after deleting this check. + local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) + }) { + match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { + // Mutable closure is used after current expr; we cannot consume it. + Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), + Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { + snippet = format!("&{snippet}"); + }, + _ => (), } - diag.span_suggestion( - expr.span, - "replace the closure with the function itself", - snippet, - Applicability::MachineApplicable, - ); } - }); - } - }, - ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { - if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) - && !cx.tcx.has_attr(method_def_id, sym::track_caller) - && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) - { - span_lint_and_then( - cx, - REDUNDANT_CLOSURE_FOR_METHOD_CALLS, - expr.span, - "redundant closure", - |diag| { - let args = typeck.node_args(body.value.hir_id); - let caller = self_.hir_id.owner.def_id; - let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); - diag.span_suggestion( - expr.span, - "replace the closure with the method itself", - format!("{}::{}", type_name, path.ident.name), - Applicability::MachineApplicable, - ); - }, - ); - } - }, - _ => (), - } + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + snippet, + Applicability::MachineApplicable, + ); + } + }); + } + }, + ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { + if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) + && !cx.tcx.has_attr(method_def_id, sym::track_caller) + && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.span, + "redundant closure", + |diag| { + let args = typeck.node_args(body.value.hir_id); + let caller = self_.hir_id.owner.def_id; + let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); + diag.span_suggestion( + expr.span, + "replace the closure with the method itself", + format!("{}::{}", type_name, path.ident.name), + Applicability::MachineApplicable, + ); + }, + ); + } + }, + _ => (), } } @@ -251,12 +275,8 @@ fn check_inputs( }) } -fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, call_sig: FnSig<'_>) -> bool { - call_sig.safety == Safety::Safe - && !has_late_bound_to_non_late_bound_regions( - cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(), - call_sig, - ) +fn check_sig<'tcx>(closure_sig: FnSig<'tcx>, call_sig: FnSig<'tcx>) -> bool { + call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions(closure_sig, call_sig) } /// This walks through both signatures and checks for any time a late-bound region is expected by an @@ -265,7 +285,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, c /// This is needed because rustc is unable to late bind early-bound regions in a function signature. fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { - matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..)) + from_region.is_bound() && !to_region.is_bound() } fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { @@ -318,3 +338,8 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<' .zip(to_sig.inputs_and_output) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) } + +fn ty_has_static(ty: Ty<'_>) -> bool { + ty.walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if re.is_static())) +} diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 0f4176ec73bb2..9bf3baba4b597 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -88,11 +88,11 @@ impl LateLintPass<'_> for ExhaustiveItems { && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)) && fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public()) { - let suggestion_span = item.span.shrink_to_lo(); - let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); span_lint_and_then(cx, lint, item.span, msg, |diag| { + let suggestion_span = item.span.shrink_to_lo(); + let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); let sugg = format!("#[non_exhaustive]\n{indent}"); - diag.span_suggestion( + diag.span_suggestion_verbose( suggestion_span, "try adding #[non_exhaustive]", sugg, diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs index bb74e345703ff..95b8e882da792 100644 --- a/clippy_lints/src/field_scoped_visibility_modifiers.rs +++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -62,13 +62,15 @@ impl EarlyLintPass for FieldScopedVisibilityModifiers { // pub(self) is equivalent to not using pub at all, so we ignore it continue; } - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, FIELD_SCOPED_VISIBILITY_MODIFIERS, field.vis.span, "scoped visibility modifier on a field", - None, - "consider making the field private and adding a scoped visibility method for it", + |diag| { + diag.help("consider making the field private and adding a scoped visibility method for it"); + }, ); } } diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 6adcd2235dc52..f095c1add91f8 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,7 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::numeric_literal; use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FloatTy}; @@ -105,32 +105,43 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { if is_whole && !sym_str.contains(['e', 'E']) { // Normalize the literal by stripping the fractional portion if sym_str.split('.').next().unwrap() != float_str { - // If the type suffix is missing the suggestion would be - // incorrectly interpreted as an integer so adding a `.0` - // suffix to prevent that. - if type_suffix.is_none() { - float_str.push_str(".0"); - } - - span_lint_and_sugg( + span_lint_and_then( cx, LOSSY_FLOAT_LITERAL, expr.span, "literal cannot be represented as the underlying type without loss of precision", - "consider changing the type or replacing it with", - numeric_literal::format(&float_str, type_suffix, true), - Applicability::MachineApplicable, + |diag| { + // If the type suffix is missing the suggestion would be + // incorrectly interpreted as an integer so adding a `.0` + // suffix to prevent that. + if type_suffix.is_none() { + float_str.push_str(".0"); + } + diag.span_suggestion_with_style( + expr.span, + "consider changing the type or replacing it with", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways, + ); + }, ); } } else if digits > max as usize && float_str.len() < sym_str.len() { - span_lint_and_sugg( + span_lint_and_then( cx, EXCESSIVE_PRECISION, expr.span, "float has excessive precision", - "consider changing the type or truncating it to", - numeric_literal::format(&float_str, type_suffix, true), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion_with_style( + expr.span, + "consider changing the type or truncating it to", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways, + ); + }, ); } } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 68bdf88d0a7e4..bf4bcabfe8911 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -1,9 +1,9 @@ use clippy_utils::consts::Constant::{Int, F32, F64}; -use clippy_utils::consts::{constant, constant_simple, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::{ - eq_expr_value, get_parent_expr, higher, in_constant, is_inherent_method_call, is_no_std_crate, numeric_literal, - peel_blocks, sugg, + eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate, + numeric_literal, peel_blocks, sugg, }; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; @@ -112,7 +112,7 @@ declare_lint_pass!(FloatingPointArithmetic => [ // Returns the specialized log method for a given base if base is constant // and is one of 2, 10 and e fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { - if let Some(value) = constant(cx, cx.typeck_results(), base) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(base) { if F32(2.0) == value || F64(2.0) == value { return Some("log2"); } else if F32(10.0) == value || F64(10.0) == value { @@ -182,10 +182,8 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { rhs, ) = receiver.kind { - let recv = match ( - constant(cx, cx.typeck_results(), lhs), - constant(cx, cx.typeck_results(), rhs), - ) { + let ecx = ConstEvalCtxt::new(cx); + let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, _ => return, @@ -230,7 +228,7 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option { fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { // Check receiver - if let Some(value) = constant(cx, cx.typeck_results(), receiver) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) { if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { Some("exp") } else if F32(2.0) == value || F64(2.0) == value { @@ -251,7 +249,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } // Check argument - if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { ( SUBOPTIMAL_FLOPS, @@ -291,7 +289,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { if value == Int(2) { if let Some(parent) = get_parent_expr(cx, expr) { if let Some(grandparent) = get_parent_expr(cx, parent) { @@ -397,8 +395,9 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { ) = &add_rhs.kind && lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi" - && let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1) - && let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1) + && let ecx = ConstEvalCtxt::new(cx) + && let Some(lvalue) = ecx.eval(largs_1) + && let Some(rvalue) = ecx.eval(rargs_1) && Int(2) == lvalue && Int(2) == rvalue { @@ -438,7 +437,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { rhs, ) = expr.kind && cx.typeck_results().expr_ty(lhs).is_floating_point() - && let Some(value) = constant(cx, cx.typeck_results(), rhs) + && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) && (F32(1.0) == value || F64(1.0) == value) && let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind && cx.typeck_results().expr_ty(self_arg).is_floating_point() @@ -552,7 +551,7 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - /// Returns true iff expr is some zero literal fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match constant_simple(cx, cx.typeck_results(), expr) { + match ConstEvalCtxt::new(cx).eval_simple(expr) { Some(Int(i)) => i == 0, Some(F32(f)) => f == 0.0, Some(F64(f)) => f == 0.0, @@ -696,8 +695,9 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { mul_lhs, mul_rhs, ) = &div_lhs.kind - && let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs) - && let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs) + && let ecx = ConstEvalCtxt::new(cx) + && let Some(rvalue) = ecx.eval(div_rhs) + && let Some(lvalue) = ecx.eval(mul_rhs) { // TODO: also check for constant values near PI/180 or 180/PI if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) @@ -753,7 +753,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // All of these operations are currently not const and are in std. - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return; } diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index a75538dd329b5..d05c5a01f41ce 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_lang_item; use clippy_utils::{higher, match_def_path, paths}; use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource}; @@ -81,13 +81,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString { _ => return, }; if is_format(cx, arg) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, FORMAT_PUSH_STRING, expr.span, "`format!(..)` appended to existing `String`", - None, - "consider using `write!` to avoid the extra allocation", + |diag| { + diag.help("consider using `write!` to avoid the extra allocation"); + }, ); } } diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index 9acb72b2e3729..6ab7bbc2dfc0c 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::{in_constant, is_integer_literal}; +use clippy_utils::{is_in_const_context, is_integer_literal}; use rustc_errors::Applicability; use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { && matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)) // do not lint in constant context, because the suggestion won't work. - // NB: keep this check until a new `const_trait_impl` is available and stablized. - && !in_constant(cx, exp.hir_id) + // NB: keep this check until a new `const_trait_impl` is available and stabilized. + && !is_in_const_context(cx) { let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind { let ty = cx.typeck_results().expr_ty(expr); diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index b38cc7b36a123..1c52514a330d4 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{higher, SpanlessEq}; +use clippy_utils::{eq_expr_value, higher}; use core::ops::ControlFlow; use rustc_errors::Diag; use rustc_hir::{Expr, ExprKind}; @@ -51,53 +51,45 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { if_else: Some(if_else), .. }) = higher::IfLet::hir(cx, expr) + && let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None)) + && let Some(arm_mutex) = + for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex))) { - let is_mutex_lock = |e: &'tcx Expr<'tcx>| { - if let Some(mutex) = is_mutex_lock_call(cx, e) { - ControlFlow::Break(mutex) - } else { - ControlFlow::Continue(()) - } + let diag = |diag: &mut Diag<'_, ()>| { + diag.span_label( + op_mutex.span, + "this Mutex will remain locked for the entire `if let`-block...", + ); + diag.span_label( + arm_mutex.span, + "... and is tried to lock again here, which will always deadlock.", + ); + diag.help("move the lock call outside of the `if let ...` expression"); }; - - let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock); - if let Some(op_mutex) = op_mutex { - let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock); - if let Some(arm_mutex) = arm_mutex - && SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex) - { - let diag = |diag: &mut Diag<'_, ()>| { - diag.span_label( - op_mutex.span, - "this Mutex will remain locked for the entire `if let`-block...", - ); - diag.span_label( - arm_mutex.span, - "... and is tried to lock again here, which will always deadlock.", - ); - diag.help("move the lock call outside of the `if let ...` expression"); - }; - span_lint_and_then( - cx, - IF_LET_MUTEX, - expr.span, - "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", - diag, - ); - } - } + span_lint_and_then( + cx, + IF_LET_MUTEX, + expr.span, + "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", + diag, + ); } } } -fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { +fn mutex_lock_call<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op_mutex: Option<&'tcx Expr<'_>>, +) -> ControlFlow<&'tcx Expr<'tcx>> { if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind && path.ident.as_str() == "lock" && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() && is_type_diagnostic_item(cx, ty, sym::Mutex) + && op_mutex.map_or(true, |op| eq_expr_value(cx, self_arg, op)) { - Some(self_arg) + ControlFlow::Break(self_arg) } else { - None + ControlFlow::Continue(()) } } diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 2f6daeeb90d9d..0ebd8d0c237b6 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -1,7 +1,7 @@ //! lint on if branches that could be swapped so no `!` operation is necessary //! on the condition -use clippy_utils::consts::{constant_simple, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_else_clause; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; @@ -49,7 +49,7 @@ declare_clippy_lint! { declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { - if let Some(value) = constant_simple(cx, cx.typeck_results(), expr) { + if let Some(value) = ConstEvalCtxt::new(cx).eval_simple(expr) { return Constant::Int(0) == value; } false diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 39ea16b05d1ad..0bca53c1536de 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,10 +1,12 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::{contains_return, higher, in_constant, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{ + contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks, +}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; @@ -76,37 +78,44 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !is_else_clause(cx.tcx, expr) - && !in_constant(cx, expr.hir_id) + && !is_in_const_context(cx) && !in_external_macro(cx.sess(), expr.span) && self.msrv.meets(msrvs::BOOL_THEN) && !contains_return(then_block.stmts) { - let mut app = Applicability::Unspecified; - let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) - .maybe_par() - .to_string(); - let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let mut method_body = if then_block.stmts.is_empty() { - arg_snip.into_owned() - } else { - format!("{{ /* snippet */ {arg_snip} }}") - }; let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) { "then_some" } else { - method_body.insert_str(0, "|| "); "then" }; - let help = - format!("consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`",); - span_lint_and_help( + span_lint_and_then( cx, IF_THEN_SOME_ELSE_NONE, expr.span, format!("this could be simplified with `bool::{method_name}`"), - None, - help, + |diag| { + let mut app = Applicability::Unspecified; + let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) + .maybe_par() + .to_string(); + let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; + let method_body = if let Some(first_stmt) = then_block.stmts.first() { + let (block_snippet, _) = + snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app); + let closure = if method_name == "then" { "|| " } else { "" }; + format!("{closure} {{ {block_snippet}; {arg_snip} }}") + } else { + arg_snip.into_owned() + }; + + diag.span_suggestion( + expr.span, + "try", + format!("{cond_snip}.{method_name}({method_body})"), + app, + ); + }, ); } } diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 344a04e6e7e82..e56f33f8dcfe2 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; -use rustc_errors::Diag; +use rustc_errors::{Applicability, Diag}; use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor}; use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind}; @@ -13,7 +13,7 @@ use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; use rustc_span::Span; -use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt}; use clippy_utils::ty::is_type_diagnostic_item; @@ -77,33 +77,32 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { &generics_snip[1..generics_snip.len() - 1] }; - multispan_sugg( - diag, - "consider adding a type parameter", - vec![ - ( - generics_suggestion_span, - format!( - "<{generics_snip}{}S: ::std::hash::BuildHasher{}>", - if generics_snip.is_empty() { "" } else { ", " }, - if vis.suggestions.is_empty() { - "" - } else { - // request users to add `Default` bound so that generic constructors can be used - " + Default" - }, - ), - ), - ( - target.span(), - format!("{}<{}, S>", target.type_name(), target.type_arguments(),), + let mut suggestions = vec![ + ( + generics_suggestion_span, + format!( + "<{generics_snip}{}S: ::std::hash::BuildHasher{}>", + if generics_snip.is_empty() { "" } else { ", " }, + if vis.suggestions.is_empty() { + "" + } else { + // request users to add `Default` bound so that generic constructors can be used + " + Default" + }, ), - ], + ), + ( + target.span(), + format!("{}<{}, S>", target.type_name(), target.type_arguments(),), + ), + ]; + suggestions.extend(vis.suggestions); + + diag.multipart_suggestion( + "add a type parameter for `BuildHasher`", + suggestions, + Applicability::MaybeIncorrect, ); - - if !vis.suggestions.is_empty() { - multispan_sugg(diag, "...and use generic constructor", vis.suggestions); - } } if !cx.effective_visibilities.is_exported(item.owner_id.def_id) { diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index a102b434cfabb..b926e1e62ba0c 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -3,7 +3,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context, wal use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro}; use core::ops::ControlFlow; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::intravisit::FnKind; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -45,8 +45,6 @@ declare_clippy_lint! { declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_applicability(cx, span, "..", &mut app); span_lint_hir_and_then( cx, IMPLICIT_RETURN, @@ -54,14 +52,20 @@ fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { span, "missing `return` statement", |diag| { - diag.span_suggestion(span, "add `return` as shown", format!("return {snip}"), app); + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_applicability(cx, span, "..", &mut app); + diag.span_suggestion_with_style( + span, + "add `return` as shown", + format!("return {snip}"), + app, + SuggestionStyle::ShowAlways, + ); }, ); } fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; span_lint_hir_and_then( cx, IMPLICIT_RETURN, @@ -69,11 +73,14 @@ fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, exp break_span, "missing `return` statement", |diag| { - diag.span_suggestion( + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; + diag.span_suggestion_with_style( break_span, "change `break` to `return` as shown", format!("return {snip}"), app, + SuggestionStyle::ShowAlways, ); }, ); diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index f225c6e7f049f..dd5908553e59b 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; @@ -117,11 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option { fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> { if let ExprKind::Binary(op, l, r) = expr.kind { - let tr = cx.typeck_results(); - if let Some(Constant::Int(c)) = constant(cx, tr, r) { + let ecx = ConstEvalCtxt::new(cx); + if let Some(Constant::Int(c)) = ecx.eval(r) { return Some((c, op.node, l)); }; - if let Some(Constant::Int(c)) = constant(cx, tr, l) { + if let Some(Constant::Int(c)) = ecx.eval(l) { return Some((c, invert_op(op.node)?, r)); } } diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 12ca6d43b27bc..0ef5b803a89d6 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -55,7 +55,6 @@ impl IncompatibleMsrv { } } - #[allow(clippy::cast_lossless)] fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { if let Some(version) = self.is_above_msrv.get(&def_id) { return *version; @@ -67,9 +66,9 @@ impl IncompatibleMsrv { since: StableSince::Version(version), .. } => Some(RustcVersion::new( - version.major as _, - version.minor as _, - version.patch as _, + version.major.into(), + version.minor.into(), + version.patch.into(), )), _ => None, }) { diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 526b4e1fba0e9..2f9661c9ea385 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLet; use clippy_utils::ty::is_copy; @@ -246,7 +246,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { && let parent_id = cx.tcx.parent_hir_id(expr.hir_id) && let hir::Node::Expr(parent_expr) = cx.tcx.hir_node(parent_id) && let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind - && let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr) + && let Some(Constant::Int(index_value)) = ConstEvalCtxt::new(cx).eval(index_expr) && let Ok(index_value) = index_value.try_into() && index_value < max_suggested_slice diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 6729c7c8d1018..3ac50b8f1fba5 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -1,7 +1,7 @@ //! lint on indexing and slicing operations use clippy_config::Conf; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::ty::{deref_chain, get_adt_inherent_method}; use clippy_utils::{higher, is_from_proc_macro}; @@ -70,8 +70,6 @@ declare_clippy_lint! { /// /// Use instead: /// ```no_run - /// # #![allow(unused)] - /// /// # let x = vec![0; 5]; /// # let y = [0, 1, 2, 3]; /// x.get(2); @@ -179,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { return; } // Index is a constant uint. - if let Some(constant) = constant(cx, cx.typeck_results(), index) { + if let Some(constant) = ConstEvalCtxt::new(cx).eval(index) { // only `usize` index is legal in rust array index // leave other type to rustc if let Constant::Int(off) = constant @@ -217,14 +215,15 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { /// Returns a tuple of options with the start and end (exclusive) values of /// the range. If the start or end is not constant, None is returned. fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option, Option) { - let s = range.start.map(|expr| constant(cx, cx.typeck_results(), expr)); + let ecx = ConstEvalCtxt::new(cx); + let s = range.start.map(|expr| ecx.eval(expr)); let start = match s { Some(Some(Constant::Int(x))) => Some(x), Some(_) => None, None => Some(0), }; - let e = range.end.map(|expr| constant(cx, cx.typeck_results(), expr)); + let e = range.end.map(|expr| ecx.eval(expr)); let end = match e { Some(Some(Constant::Int(x))) => { if range.limits == RangeLimits::Closed { diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index fa7e7f6b76d16..676d50c4951b1 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -41,7 +41,6 @@ declare_clippy_lint! { /// ### Example /// ```no_run /// let infinite_iter = 0..; - /// # #[allow(unused)] /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 0d3786dad4b1e..9eed7aa924339 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,6 +1,6 @@ //! lint on inherent implementations -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_lint_allowed; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::LocalDefId; @@ -105,13 +105,14 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. lint_spans.sort_by_key(|x| x.0.lo()); for (span, first_span) in lint_spans { - span_lint_and_note( + span_lint_and_then( cx, MULTIPLE_INHERENT_IMPL, span, "multiple implementations of this structure", - Some(first_span), - "first implementation here", + |diag| { + diag.span_note(first_span, "first implementation here"); + }, ); } } diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index 30f2285bdd235..1929fbded3b56 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -7,7 +7,7 @@ use rustc_span::Span; use clippy_utils::comparisons; use clippy_utils::comparisons::Rel; -use clippy_utils::consts::{constant_full_int, FullInt}; +use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint; use clippy_utils::source::snippet; @@ -95,7 +95,7 @@ fn upcast_comparison_bounds_err<'tcx>( invert: bool, ) { if let Some((lb, ub)) = lhs_bounds { - if let Some(norm_rhs_val) = constant_full_int(cx, cx.typeck_results(), rhs) { + if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) { if rel == Rel::Eq || rel == Rel::Ne { if norm_rhs_val < lb || norm_rhs_val > ub { err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index c67da689aaee0..f2f841dcec33d 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; @@ -66,16 +66,18 @@ impl LateLintPass<'_> for LargeIncludeFile { && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) { - span_lint_and_note( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LARGE_INCLUDE_FILE, expr.span.source_callsite(), "attempted to include a large file", - None, - format!( - "the configuration allows a maximum size of {} bytes", - self.max_file_size - ), + |diag| { + diag.note(format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + )); + }, ); } } diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 8fa63f3e8fde0..b522c22a44d70 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type}; use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths}; use rustc_hir::{LetStmt, LocalSource, PatKind}; @@ -149,43 +149,53 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, }); if contains_sync_guard { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_LOCK, local.span, "non-binding `let` on a synchronization lock", - None, - "consider using an underscore-prefixed named \ - binding or dropping explicitly with `std::mem::drop`", + |diag| { + diag.help( + "consider using an underscore-prefixed named \ + binding or dropping explicitly with `std::mem::drop`", + ); + }, ); } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_FUTURE, local.span, "non-binding `let` on a future", - None, - "consider awaiting the future or dropping explicitly with `std::mem::drop`", + |diag| { + diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`"); + }, ); } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding `let` on an expression with `#[must_use]` type", - None, - "consider explicitly using expression value", + |diag| { + diag.help("consider explicitly using expression value"); + }, ); } else if is_must_use_func_call(cx, init) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding `let` on a result of a `#[must_use]` function", - None, - "consider explicitly using function result", + |diag| { + diag.help("consider explicitly using function result"); + }, ); } @@ -204,18 +214,22 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { return; } - span_lint_and_help( + span_lint_and_then( cx, LET_UNDERSCORE_UNTYPED, local.span, "non-binding `let` without a type annotation", - Some(Span::new( - local.pat.span.hi(), - local.pat.span.hi() + BytePos(1), - local.pat.span.ctxt(), - local.pat.span.parent(), - )), - "consider adding a type annotation", + |diag| { + diag.span_help( + Span::new( + local.pat.span.hi(), + local.pat.span.hi() + BytePos(1), + local.pat.span.ctxt(), + local.pat.span.parent(), + ), + "consider adding a type annotation", + ); + }, ); } } diff --git a/clippy_lints/src/lib.deprecated.rs b/clippy_lints/src/lib.deprecated.rs deleted file mode 100644 index 0d21261822dd0..0000000000000 --- a/clippy_lints/src/lib.deprecated.rs +++ /dev/null @@ -1,78 +0,0 @@ -// This file was generated by `cargo dev update_lints`. -// Use that command to update this file and do not edit by hand. -// Manual edits will be overwritten. - -{ - store.register_removed( - "clippy::should_assert_eq", - "`assert!()` will be more flexible with RFC 2011", - ); - store.register_removed( - "clippy::extend_from_slice", - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", - ); - store.register_removed( - "clippy::range_step_by_zero", - "`iterator.step_by(0)` panics nowadays", - ); - store.register_removed( - "clippy::unstable_as_slice", - "`Vec::as_slice` has been stabilized in 1.7", - ); - store.register_removed( - "clippy::unstable_as_mut_slice", - "`Vec::as_mut_slice` has been stabilized in 1.7", - ); - store.register_removed( - "clippy::misaligned_transmute", - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", - ); - store.register_removed( - "clippy::assign_ops", - "using compound assignment operators (e.g., `+=`) is harmless", - ); - store.register_removed( - "clippy::if_let_redundant_pattern_matching", - "this lint has been changed to redundant_pattern_matching", - ); - store.register_removed( - "clippy::unsafe_vector_initialization", - "the replacement suggested by this lint had substantially different behavior", - ); - store.register_removed( - "clippy::unused_collect", - "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint", - ); - store.register_removed( - "clippy::replace_consts", - "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants", - ); - store.register_removed( - "clippy::regex_macro", - "the regex! macro has been removed from the regex crate in 2018", - ); - store.register_removed( - "clippy::find_map", - "this lint has been replaced by `manual_find_map`, a more specific lint", - ); - store.register_removed( - "clippy::filter_map", - "this lint has been replaced by `manual_filter_map`, a more specific lint", - ); - store.register_removed( - "clippy::pub_enum_variant_names", - "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items", - ); - store.register_removed( - "clippy::wrong_pub_self_convention", - "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items", - ); - store.register_removed( - "clippy::maybe_misused_cfg", - "this lint has been replaced by `unexpected_cfgs`", - ); - store.register_removed( - "clippy::mismatched_target_os", - "this lint has been replaced by `unexpected_cfgs`", - ); -} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a388b6b2eaab3..ce13a9afef5b0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -13,7 +13,6 @@ #![feature(stmt_expr_attributes)] #![feature(unwrap_infallible)] #![recursion_limit = "512"] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow( clippy::missing_docs_in_private_items, clippy::must_use_candidate, @@ -64,13 +63,11 @@ extern crate clippy_utils; #[macro_use] extern crate declare_clippy_lint; -#[cfg(feature = "internal")] -pub mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; mod declared_lints; -mod renamed_lints; +mod deprecated_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` mod absolute_paths; @@ -372,6 +369,7 @@ mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; mod unused_peekable; +mod unused_result_ok; mod unused_rounding; mod unused_self; mod unused_unit; @@ -495,7 +493,7 @@ pub fn explain(name: &str) -> i32 { // Check if the lint has configuration let mut mdconf = get_configuration_metadata(); let name = name.to_ascii_lowercase(); - mdconf.retain(|cconf| cconf.lints.contains(&name)); + mdconf.retain(|cconf| cconf.lints.contains(&&*name)); if !mdconf.is_empty() { println!("### Configuration for {}:\n", info.lint.name_lower()); for conf in mdconf { @@ -531,10 +529,14 @@ fn register_categories(store: &mut rustc_lint::LintStore) { /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { - register_removed_non_tool_lints(store); register_categories(store); - include!("lib.deprecated.rs"); + for (old_name, new_name) in deprecated_lints::RENAMED { + store.register_renamed(old_name, new_name); + } + for (name, reason) in deprecated_lints::DEPRECATED { + store.register_removed(name, reason); + } #[cfg(feature = "internal")] { @@ -669,6 +671,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf))); store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); + store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk)); store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); @@ -818,7 +821,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf))); store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); - store.register_late_pass(|_| Box::::default()); + store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); @@ -907,68 +910,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); - store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); + store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)); store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); // add lints here, do not remove this comment, it's used in `new_lint` } - -#[rustfmt::skip] -fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { - store.register_removed( - "should_assert_eq", - "`assert!()` will be more flexible with RFC 2011", - ); - store.register_removed( - "extend_from_slice", - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", - ); - store.register_removed( - "range_step_by_zero", - "`iterator.step_by(0)` panics nowadays", - ); - store.register_removed( - "unstable_as_slice", - "`Vec::as_slice` has been stabilized in 1.7", - ); - store.register_removed( - "unstable_as_mut_slice", - "`Vec::as_mut_slice` has been stabilized in 1.7", - ); - store.register_removed( - "misaligned_transmute", - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", - ); - store.register_removed( - "assign_ops", - "using compound assignment operators (e.g., `+=`) is harmless", - ); - store.register_removed( - "if_let_redundant_pattern_matching", - "this lint has been changed to redundant_pattern_matching", - ); - store.register_removed( - "unsafe_vector_initialization", - "the replacement suggested by this lint had substantially different behavior", - ); - store.register_removed( - "reverse_range_loop", - "this lint is now included in reversed_empty_ranges", - ); -} - -/// Register renamed lints. -/// -/// Used in `./src/driver.rs`. -pub fn register_renamed(ls: &mut rustc_lint::LintStore) { - for (old_name, new_name) in renamed_lints::RENAMED_LINTS { - ls.register_renamed(old_name, new_name); - } -} - -// only exists to let the dogfood integration test works. -// Don't run clippy as an executable directly -#[allow(dead_code)] -fn main() { - panic!("Please use the cargo-clippy executable"); -} diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index b685d1dad1a75..259e4d6c08fb7 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -2,13 +2,13 @@ //! floating-point literal expressions. use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::source::snippet_opt; use rustc_ast::ast::{Expr, ExprKind, LitKind}; use rustc_ast::token; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; use rustc_span::Span; @@ -159,63 +159,39 @@ enum WarningType { } impl WarningType { - fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: Span) { + fn lint_and_text(&self) -> (&'static Lint, &'static str, &'static str) { match self { - Self::MistypedLiteralSuffix => span_lint_and_sugg( - cx, + Self::MistypedLiteralSuffix => ( MISTYPED_LITERAL_SUFFIXES, - span, "mistyped literal suffix", "did you mean to write", - suggested_format, - Applicability::MaybeIncorrect, ), - Self::UnreadableLiteral => span_lint_and_sugg( - cx, - UNREADABLE_LITERAL, - span, - "long literal lacking separators", - "consider", - suggested_format, - Applicability::MachineApplicable, - ), - Self::LargeDigitGroups => span_lint_and_sugg( - cx, - LARGE_DIGIT_GROUPS, - span, - "digit groups should be smaller", - "consider", - suggested_format, - Applicability::MachineApplicable, - ), - Self::InconsistentDigitGrouping => span_lint_and_sugg( - cx, + Self::UnreadableLiteral => (UNREADABLE_LITERAL, "long literal lacking separators", "consider"), + Self::LargeDigitGroups => (LARGE_DIGIT_GROUPS, "digit groups should be smaller", "consider"), + Self::InconsistentDigitGrouping => ( INCONSISTENT_DIGIT_GROUPING, - span, "digits grouped inconsistently by underscores", "consider", - suggested_format, - Applicability::MachineApplicable, ), - Self::DecimalRepresentation => span_lint_and_sugg( - cx, + Self::DecimalRepresentation => ( DECIMAL_LITERAL_REPRESENTATION, - span, "integer literal has a better hexadecimal representation", "consider", - suggested_format, - Applicability::MachineApplicable, ), - Self::UnusualByteGroupings => span_lint_and_sugg( - cx, + Self::UnusualByteGroupings => ( UNUSUAL_BYTE_GROUPINGS, - span, "digits of hex, binary or octal literal not in groups of equal size", "consider", - suggested_format, - Applicability::MachineApplicable, ), - }; + } + } + + fn display(&self, num_lit: &NumericLiteral<'_>, cx: &EarlyContext<'_>, span: Span) { + let (lint, message, try_msg) = self.lint_and_text(); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, lint, span, message, |diag| { + diag.span_suggestion(span, try_msg, num_lit.format(), Applicability::MaybeIncorrect); + }); } } @@ -293,7 +269,7 @@ impl LiteralDigitGrouping { WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true, }; if should_warn { - warning_type.display(num_lit.format(), cx, span); + warning_type.display(&num_lit, cx, span); } } } @@ -346,11 +322,14 @@ impl LiteralDigitGrouping { } } *part = main_part; - let mut sugg = num_lit.format(); - sugg.push('_'); - sugg.push(missing_char); - sugg.push_str(last_group); - WarningType::MistypedLiteralSuffix.display(sugg, cx, span); + let (lint, message, try_msg) = WarningType::MistypedLiteralSuffix.lint_and_text(); + span_lint_and_then(cx, lint, span, message, |diag| { + let mut sugg = num_lit.format(); + sugg.push('_'); + sugg.push(missing_char); + sugg.push_str(last_group); + diag.span_suggestion(span, try_msg, sugg, Applicability::MaybeIncorrect); + }); false } else { true @@ -471,7 +450,7 @@ impl DecimalLiteralRepresentation { let hex = format!("{val:#X}"); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { - warning_type.display(num_lit.format(), cx, span); + warning_type.display(&num_lit, cx, span); }); } } diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index f0ee64d714e05..73a23615c2dc7 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -2,6 +2,7 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_block, is_integer_const}; +use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::{Expr, Pat}; @@ -17,6 +18,7 @@ pub(super) fn check<'tcx>( arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>, expr: &'tcx Expr<'_>, + label: Option