From e7fbf7223f640c5121b21183cd26ecd3ccb6a820 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 20 Mar 2024 01:27:33 -0700 Subject: [PATCH 001/214] improve codegen of fmt_num to delete unreachable panic it seems LLVM doesn't realize that `curr` is always decremented at least once in either loop formatting characters of the input string by their appropriate radix, and so the later `&buf[curr..]` generates a check for out-of-bounds access and panic. this is unreachable in reality as even for `x == T::zero()` we'll produce at least the character `Self::digit(T::zero())` for at least one character output, and `curr` will always be at least one below `buf.len()`. adjust `fmt_int` to make this fact more obvious to the compiler, which fortunately (or unfortunately) results in a measurable performance improvement for workloads heavy on formatting integers. --- library/core/src/fmt/num.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index ab2158394bf1..abe833a4bf2d 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -79,11 +79,11 @@ unsafe trait GenericRadix: Sized { if is_nonnegative { // Accumulate each digit of the number from the least significant // to the most significant figure. - for byte in buf.iter_mut().rev() { + loop { let n = x % base; // Get the current place value. x = x / base; // Deaccumulate the number. - byte.write(Self::digit(n.to_u8())); // Store the digit in the buffer. curr -= 1; + buf[curr].write(Self::digit(n.to_u8())); // Store the digit in the buffer. if x == zero { // No more digits left to accumulate. break; @@ -91,11 +91,11 @@ unsafe trait GenericRadix: Sized { } } else { // Do the same as above, but accounting for two's complement. - for byte in buf.iter_mut().rev() { + loop { let n = zero - (x % base); // Get the current place value. x = x / base; // Deaccumulate the number. - byte.write(Self::digit(n.to_u8())); // Store the digit in the buffer. curr -= 1; + buf[curr].write(Self::digit(n.to_u8())); // Store the digit in the buffer. if x == zero { // No more digits left to accumulate. break; From cea973f857b63e7c208e822d95c06c4883062449 Mon Sep 17 00:00:00 2001 From: iximeow Date: Sat, 23 Mar 2024 11:33:09 -0700 Subject: [PATCH 002/214] try adding a test that LowerHex and friends don't panic, but it doesn't work --- library/core/benches/fmt.rs | 14 ++++++++++++++ tests/codegen/fmt_int_no_panic.rs | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/codegen/fmt_int_no_panic.rs diff --git a/library/core/benches/fmt.rs b/library/core/benches/fmt.rs index d1cdb12e50f8..a02bd6065425 100644 --- a/library/core/benches/fmt.rs +++ b/library/core/benches/fmt.rs @@ -148,3 +148,17 @@ fn write_u64_min(bh: &mut Bencher) { test::black_box(format!("{}", 0u64)); }); } + +#[bench] +fn write_u8_max(bh: &mut Bencher) { + bh.iter(|| { + test::black_box(format!("{}", u8::MAX)); + }); +} + +#[bench] +fn write_u8_min(bh: &mut Bencher) { + bh.iter(|| { + test::black_box(format!("{}", 0u8)); + }); +} diff --git a/tests/codegen/fmt_int_no_panic.rs b/tests/codegen/fmt_int_no_panic.rs new file mode 100644 index 000000000000..efd0530bc888 --- /dev/null +++ b/tests/codegen/fmt_int_no_panic.rs @@ -0,0 +1,24 @@ +// Trying to check that formatting u8/u32/u64/etc do not panic. +// +// This test does not correctly do so yet. + +//@ compile-flags: -O + +#![crate_type = "lib"] + +// expected to need to write some kind of `impl core::fmt::Write` on a struct like this to avoid +// unrelated panics if `String::write_str` can't make space.. +// struct CanAlwaysBeWrittenTo; + +use std::fmt::Write; + +// CHECK-LABEL: @format_int_doesnt_panic +#[no_mangle] +pub fn format_int_doesnt_panic(s: &mut String) -> std::fmt::Result { + // CHECK-NOT: panic + // ... but wait! this will definitely panic if `s.vec.reserve_for_push()` cannot alloc! this + // shouldn't pass! + write!(s, "{:x}", 0u8)?; + write!(s, "{:x}", u8::MAX)?; + Ok(()) +} From 6a5a88eb6e7a3494902f95b07d074621fb69cd8b Mon Sep 17 00:00:00 2001 From: ilikdoge Date: Wed, 24 Jul 2024 19:07:14 -0700 Subject: [PATCH 003/214] Implement `mixed_integer_ops_unsigned_sub` --- library/core/src/num/uint_macros.rs | 103 ++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index e6bdc4d450d4..7d39040a6a20 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -765,6 +765,33 @@ macro_rules! uint_impl { } } + /// Checked subtraction with a signed integer. Computes `self - rhs`, + /// returning `None` if overflow occurred. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(mixed_integer_ops_unsigned_sub)] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".checked_sub_signed(2), None);")] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".checked_sub_signed(-2), Some(3));")] + #[doc = concat!("assert_eq!((", stringify!($SelfT), "::MAX - 2).checked_sub_signed(-4), None);")] + /// ``` + #[unstable(feature = "mixed_integer_ops_unsigned_sub", issue = "126043")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_sub_signed(self, rhs: $SignedT) -> Option { + let (res, overflow) = self.overflowing_sub_signed(rhs); + + if !overflow { + Some(res) + } else { + None + } + } + #[doc = concat!( "Checked integer subtraction. Computes `self - rhs` and checks if the result fits into an [`", stringify!($SignedT), "`], returning `None` if overflow occurred." @@ -1757,6 +1784,35 @@ macro_rules! uint_impl { intrinsics::saturating_sub(self, rhs) } + /// Saturating integer subtraction. Computes `self` - `rhs`, saturating at + /// the numeric bounds instead of overflowing. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(mixed_integer_ops_unsigned_sub)] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".saturating_sub_signed(2), 0);")] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".saturating_sub_signed(-2), 3);")] + #[doc = concat!("assert_eq!((", stringify!($SelfT), "::MAX - 2).saturating_sub_signed(-4), ", stringify!($SelfT), "::MAX);")] + /// ``` + #[unstable(feature = "mixed_integer_ops_unsigned_sub", issue = "126043")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn saturating_sub_signed(self, rhs: $SignedT) -> Self { + let (res, overflow) = self.overflowing_sub_signed(rhs); + + if !overflow { + res + } else if rhs < 0 { + Self::MAX + } else { + 0 + } + } + /// Saturating integer multiplication. Computes `self * rhs`, /// saturating at the numeric bounds instead of overflowing. /// @@ -1890,6 +1946,27 @@ macro_rules! uint_impl { intrinsics::wrapping_sub(self, rhs) } + /// Wrapping (modular) subtraction with a signed integer. Computes + /// `self - rhs`, wrapping around at the boundary of the type. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(mixed_integer_ops_unsigned_sub)] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".wrapping_sub_signed(2), ", stringify!($SelfT), "::MAX);")] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".wrapping_sub_signed(-2), 3);")] + #[doc = concat!("assert_eq!((", stringify!($SelfT), "::MAX - 2).wrapping_sub_signed(-4), 1);")] + /// ``` + #[unstable(feature = "mixed_integer_ops_unsigned_sub", issue = "126043")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn wrapping_sub_signed(self, rhs: $SignedT) -> Self { + self.wrapping_sub(rhs as Self) + } + /// Wrapping (modular) multiplication. Computes `self * /// rhs`, wrapping around at the boundary of the type. /// @@ -2328,6 +2405,32 @@ macro_rules! uint_impl { (c, b || d) } + /// Calculates `self` - `rhs` with a signed `rhs` + /// + /// Returns a tuple of the subtraction along with a boolean indicating + /// whether an arithmetic overflow would occur. If an overflow would + /// have occurred then the wrapped value is returned. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(mixed_integer_ops_unsigned_sub)] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".overflowing_sub_signed(2), (", stringify!($SelfT), "::MAX, true));")] + #[doc = concat!("assert_eq!(1", stringify!($SelfT), ".overflowing_sub_signed(-2), (3, false));")] + #[doc = concat!("assert_eq!((", stringify!($SelfT), "::MAX - 2).overflowing_sub_signed(-4), (1, true));")] + /// ``` + #[unstable(feature = "mixed_integer_ops_unsigned_sub", issue = "126043")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn overflowing_sub_signed(self, rhs: $SignedT) -> (Self, bool) { + let (res, overflow) = self.overflowing_sub(rhs as Self); + + (res, overflow ^ (rhs < 0)) + } + /// Computes the absolute difference between `self` and `other`. /// /// # Examples From ad3f3c7fb67329778bf26771cd4fbfc68e175daf Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 25 Jul 2024 17:53:29 +0000 Subject: [PATCH 004/214] Skip locking span interner for some syntax context checks --- compiler/rustc_span/src/lib.rs | 6 ------ compiler/rustc_span/src/span_encoding.rs | 14 +++++++++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 7c8ac3be4bec..6cb8491067d2 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -564,12 +564,6 @@ impl Span { !self.is_dummy() && sm.is_span_accessible(self) } - /// Returns `true` if this span comes from any kind of macro, desugaring or inlining. - #[inline] - pub fn from_expansion(self) -> bool { - !self.ctxt().is_root() - } - /// Returns `true` if `span` originates in a derive-macro's expansion. pub fn in_derive_expansion(self) -> bool { matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _)) diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index 53d7b7511a62..f56da203fa8c 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -305,6 +305,13 @@ impl Span { } } + /// Returns `true` if this span comes from any kind of macro, desugaring or inlining. + #[inline] + pub fn from_expansion(self) -> bool { + // If the span is fully inferred then ctxt > MAX_CTXT + self.inline_ctxt().map_or(true, |ctxt| !ctxt.is_root()) + } + /// Returns `true` if this is a dummy span with any hygienic context. #[inline] pub fn is_dummy(self) -> bool { @@ -372,9 +379,10 @@ impl Span { pub fn eq_ctxt(self, other: Span) -> bool { match (self.inline_ctxt(), other.inline_ctxt()) { (Ok(ctxt1), Ok(ctxt2)) => ctxt1 == ctxt2, - (Ok(ctxt), Err(index)) | (Err(index), Ok(ctxt)) => { - with_span_interner(|interner| ctxt == interner.spans[index].ctxt) - } + // If `inline_ctxt` returns `Ok` the context is <= MAX_CTXT. + // If it returns `Err` the span is fully interned and the context is > MAX_CTXT. + // As these do not overlap an `Ok` and `Err` result cannot have an equal context. + (Ok(_), Err(_)) | (Err(_), Ok(_)) => false, (Err(index1), Err(index2)) => with_span_interner(|interner| { interner.spans[index1].ctxt == interner.spans[index2].ctxt }), From d2cdc76256313e8f23f585107c43badb49d5473d Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Sun, 22 Sep 2024 16:58:44 -0700 Subject: [PATCH 005/214] Implement file_lock feature This adds lock(), lock_shared(), try_lock(), try_lock_shared(), and unlock() to File gated behind the file_lock feature flag --- library/std/src/fs.rs | 217 ++++++++++++++++++ library/std/src/fs/tests.rs | 80 +++++++ library/std/src/sys/pal/hermit/fs.rs | 20 ++ library/std/src/sys/pal/solid/fs.rs | 20 ++ library/std/src/sys/pal/unix/fs.rs | 37 +++ library/std/src/sys/pal/unsupported/fs.rs | 20 ++ library/std/src/sys/pal/wasi/fs.rs | 20 ++ .../std/src/sys/pal/windows/c/bindings.txt | 4 + .../std/src/sys/pal/windows/c/windows_sys.rs | 5 + library/std/src/sys/pal/windows/fs.rs | 88 +++++++ 10 files changed, 511 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index db7867337dd5..efe936843c8c 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -615,6 +615,223 @@ impl File { self.inner.datasync() } + /// Acquire an exclusive advisory lock on the file. Blocks until the lock can be acquired. + /// + /// This acquires an exclusive advisory lock; no other file handle to this file may acquire + /// another lock. + /// + /// If this file handle, or a clone of it, already holds an advisory lock the exact behavior is + /// unspecified and platform dependent, including the possibility that it will deadlock. + /// However, if this method returns, then an exclusive lock is held. + /// + /// If the file not open for writing, it is unspecified whether this function returns an error. + /// + /// Note, this is an advisory lock meant to interact with [`lock_shared`], [`try_lock`], + /// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`] + /// and [`write`] are platform specific, and it may or may not cause non-lockholders to block. + /// + /// # Platform-specific behavior + /// + /// This function currently corresponds to the `flock` function on Unix with the `LOCK_EX` flag, + /// and the `LockFileEx` function on Windows with the `LOCKFILE_EXCLUSIVE_LOCK` flag. Note that, + /// this [may change in the future][changes]. + /// + /// [changes]: io#platform-specific-behavior + /// + /// [`lock_shared`]: File::lock_shared + /// [`try_lock`]: File::try_lock + /// [`try_lock_shared`]: File::try_lock_shared + /// [`unlock`]: File::unlock + /// [`read`]: Read::read + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// #![feature(file_lock)] + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("foo.txt")?; + /// f.lock()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "file_lock", issue = "130994")] + pub fn lock(&self) -> io::Result<()> { + self.inner.lock() + } + + /// Acquire a shared advisory lock on the file. Blocks until the lock can be acquired. + /// + /// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but + /// none may hold an exclusive lock. + /// + /// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is + /// unspecified and platform dependent, including the possibility that it will deadlock. + /// However, if this method returns, then a shared lock is held. + /// + /// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`], + /// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`] + /// and [`write`] are platform specific, and it may or may not cause non-lockholders to block. + /// + /// # Platform-specific behavior + /// + /// This function currently corresponds to the `flock` function on Unix with the `LOCK_SH` flag, + /// and the `LockFileEx` function on Windows. Note that, this + /// [may change in the future][changes]. + /// + /// [changes]: io#platform-specific-behavior + /// + /// [`lock`]: File::lock + /// [`try_lock`]: File::try_lock + /// [`try_lock_shared`]: File::try_lock_shared + /// [`unlock`]: File::unlock + /// [`read`]: Read::read + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// #![feature(file_lock)] + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("foo.txt")?; + /// f.lock_shared()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "file_lock", issue = "130994")] + pub fn lock_shared(&self) -> io::Result<()> { + self.inner.lock_shared() + } + + /// Acquire an exclusive advisory lock on the file. Returns `Ok(false)` if the file is locked. + /// + /// This acquires an exclusive advisory lock; no other file handle to this file may acquire + /// another lock. + /// + /// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is + /// unspecified and platform dependent, including the possibility that it will deadlock. + /// However, if this method returns, then an exclusive lock is held. + /// + /// If the file not open for writing, it is unspecified whether this function returns an error. + /// + /// Note, this is an advisory lock meant to interact with [`lock`], [`lock_shared`], + /// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`] + /// and [`write`] are platform specific, and it may or may not cause non-lockholders to block. + /// + /// # Platform-specific behavior + /// + /// This function currently corresponds to the `flock` function on Unix with the `LOCK_EX` and + /// `LOCK_NB` flags, and the `LockFileEx` function on Windows with the `LOCKFILE_EXCLUSIVE_LOCK` + /// and `LOCKFILE_FAIL_IMMEDIATELY` flags. Note that, this + /// [may change in the future][changes]. + /// + /// [changes]: io#platform-specific-behavior + /// + /// [`lock`]: File::lock + /// [`lock_shared`]: File::lock_shared + /// [`try_lock_shared`]: File::try_lock_shared + /// [`unlock`]: File::unlock + /// [`read`]: Read::read + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// #![feature(file_lock)] + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("foo.txt")?; + /// f.try_lock()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "file_lock", issue = "130994")] + pub fn try_lock(&self) -> io::Result { + self.inner.try_lock() + } + + /// Acquire a shared advisory lock on the file. + /// Returns `Ok(false)` if the file is exclusively locked. + /// + /// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but + /// none may hold an exclusive lock. + /// + /// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is + /// unspecified and platform dependent, including the possibility that it will deadlock. + /// However, if this method returns, then a shared lock is held. + /// + /// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`], + /// [`try_lock`], and [`unlock`]. Its interactions with other methods, such as [`read`] + /// and [`write`] are platform specific, and it may or may not cause non-lockholders to block. + /// + /// # Platform-specific behavior + /// + /// This function currently corresponds to the `flock` function on Unix with the `LOCK_SH` and + /// `LOCK_NB` flags, and the `LockFileEx` function on Windows with the + /// `LOCKFILE_FAIL_IMMEDIATELY` flag. Note that, this + /// [may change in the future][changes]. + /// + /// [changes]: io#platform-specific-behavior + /// + /// [`lock`]: File::lock + /// [`lock_shared`]: File::lock_shared + /// [`try_lock`]: File::try_lock + /// [`unlock`]: File::unlock + /// [`read`]: Read::read + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// #![feature(file_lock)] + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("foo.txt")?; + /// f.try_lock_shared()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "file_lock", issue = "130994")] + pub fn try_lock_shared(&self) -> io::Result { + self.inner.try_lock_shared() + } + + /// Release all locks on the file. + /// + /// All remaining locks are released when the file handle, and all clones of it, are dropped. + /// + /// # Platform-specific behavior + /// + /// This function currently corresponds to the `flock` function on Unix with the `LOCK_UN` flag, + /// and the `UnlockFile` function on Windows. Note that, this + /// [may change in the future][changes]. + /// + /// [changes]: io#platform-specific-behavior + /// + /// # Examples + /// + /// ```no_run + /// #![feature(file_lock)] + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("foo.txt")?; + /// f.lock()?; + /// f.unlock()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "file_lock", issue = "130994")] + pub fn unlock(&self) -> io::Result<()> { + self.inner.unlock() + } + /// Truncates or extends the underlying file, updating the size of /// this file to become `size`. /// diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 0672fe6f7718..fe3dc4adff0b 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -203,6 +203,86 @@ fn file_test_io_seek_and_write() { assert!(read_str == final_msg); } +#[test] +fn file_lock_multiple_shared() { + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_multiple_shared_test.txt"); + let f1 = check!(File::create(filename)); + let f2 = check!(OpenOptions::new().write(true).open(filename)); + + // Check that we can acquire concurrent shared locks + check!(f1.lock_shared()); + check!(f2.lock_shared()); + check!(f1.unlock()); + check!(f2.unlock()); + assert!(check!(f1.try_lock_shared())); + assert!(check!(f2.try_lock_shared())); +} + +#[test] +fn file_lock_blocking() { + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_blocking_test.txt"); + let f1 = check!(File::create(filename)); + let f2 = check!(OpenOptions::new().write(true).open(filename)); + + // Check that shared locks block exclusive locks + check!(f1.lock_shared()); + assert!(!check!(f2.try_lock())); + check!(f1.unlock()); + + // Check that exclusive locks block shared locks + check!(f1.lock()); + assert!(!check!(f2.try_lock_shared())); +} + +#[test] +fn file_lock_drop() { + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_dup_test.txt"); + let f1 = check!(File::create(filename)); + let f2 = check!(OpenOptions::new().write(true).open(filename)); + + // Check that locks are released when the File is dropped + check!(f1.lock_shared()); + assert!(!check!(f2.try_lock())); + drop(f1); + assert!(check!(f2.try_lock())); +} + +#[test] +fn file_lock_dup() { + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_dup_test.txt"); + let f1 = check!(File::create(filename)); + let f2 = check!(OpenOptions::new().write(true).open(filename)); + + // Check that locks are not dropped if the File has been cloned + check!(f1.lock_shared()); + assert!(!check!(f2.try_lock())); + let cloned = check!(f1.try_clone()); + drop(f1); + assert!(!check!(f2.try_lock())); + drop(cloned) +} + +#[test] +#[cfg(windows)] +fn file_lock_double_unlock() { + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_double_unlock_test.txt"); + let f1 = check!(File::create(filename)); + let f2 = check!(OpenOptions::new().write(true).open(filename)); + + // On Windows a file handle may acquire both a shared and exclusive lock. + // Check that both are released by unlock() + check!(f1.lock()); + check!(f1.lock_shared()); + assert!(!check!(f2.try_lock())); + check!(f1.unlock()); + assert!(check!(f2.try_lock())); +} + #[test] fn file_test_io_seek_shakedown() { // 01234567890123 diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index 70f6981f7175..17d15ed2e504 100644 --- a/library/std/src/sys/pal/hermit/fs.rs +++ b/library/std/src/sys/pal/hermit/fs.rs @@ -364,6 +364,26 @@ impl File { self.fsync() } + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> io::Result { + unsupported() + } + + pub fn try_lock_shared(&self) -> io::Result { + unsupported() + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + pub fn truncate(&self, _size: u64) -> io::Result<()> { Err(Error::from_raw_os_error(22)) } diff --git a/library/std/src/sys/pal/solid/fs.rs b/library/std/src/sys/pal/solid/fs.rs index bce9aa6d99cd..776a96ff3b7b 100644 --- a/library/std/src/sys/pal/solid/fs.rs +++ b/library/std/src/sys/pal/solid/fs.rs @@ -350,6 +350,26 @@ impl File { self.flush() } + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> io::Result { + unsupported() + } + + pub fn try_lock_shared(&self) -> io::Result { + unsupported() + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + pub fn truncate(&self, _size: u64) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs index 39aabf0b2d67..865105421949 100644 --- a/library/std/src/sys/pal/unix/fs.rs +++ b/library/std/src/sys/pal/unix/fs.rs @@ -1254,6 +1254,43 @@ impl File { } } + pub fn lock(&self) -> io::Result<()> { + cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?; + return Ok(()); + } + + pub fn lock_shared(&self) -> io::Result<()> { + cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?; + return Ok(()); + } + + pub fn try_lock(&self) -> io::Result { + let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }); + if let Err(ref err) = result { + if err.kind() == io::ErrorKind::WouldBlock { + return Ok(false); + } + } + result?; + return Ok(true); + } + + pub fn try_lock_shared(&self) -> io::Result { + let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) }); + if let Err(ref err) = result { + if err.kind() == io::ErrorKind::WouldBlock { + return Ok(false); + } + } + result?; + return Ok(true); + } + + pub fn unlock(&self) -> io::Result<()> { + cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?; + return Ok(()); + } + pub fn truncate(&self, size: u64) -> io::Result<()> { let size: off64_t = size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; diff --git a/library/std/src/sys/pal/unsupported/fs.rs b/library/std/src/sys/pal/unsupported/fs.rs index 474c9fe97d18..9585ec24f687 100644 --- a/library/std/src/sys/pal/unsupported/fs.rs +++ b/library/std/src/sys/pal/unsupported/fs.rs @@ -198,6 +198,26 @@ impl File { self.0 } + pub fn lock(&self) -> io::Result<()> { + self.0 + } + + pub fn lock_shared(&self) -> io::Result<()> { + self.0 + } + + pub fn try_lock(&self) -> io::Result { + self.0 + } + + pub fn try_lock_shared(&self) -> io::Result { + self.0 + } + + pub fn unlock(&self) -> io::Result<()> { + self.0 + } + pub fn truncate(&self, _size: u64) -> io::Result<()> { self.0 } diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs index 59ecc45b06a1..3296c762cca2 100644 --- a/library/std/src/sys/pal/wasi/fs.rs +++ b/library/std/src/sys/pal/wasi/fs.rs @@ -420,6 +420,26 @@ impl File { self.fd.datasync() } + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> io::Result { + unsupported() + } + + pub fn try_lock_shared(&self) -> io::Result { + unsupported() + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + pub fn truncate(&self, size: u64) -> io::Result<()> { self.fd.filestat_set_size(size) } diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 9c2e4500da06..d54ec9820dca 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2349,6 +2349,9 @@ Windows.Win32.Storage.FileSystem.GetFinalPathNameByHandleW Windows.Win32.Storage.FileSystem.GetFullPathNameW Windows.Win32.Storage.FileSystem.GetTempPathW Windows.Win32.Storage.FileSystem.INVALID_FILE_ATTRIBUTES +Windows.Win32.Storage.FileSystem.LOCKFILE_EXCLUSIVE_LOCK +Windows.Win32.Storage.FileSystem.LOCKFILE_FAIL_IMMEDIATELY +Windows.Win32.Storage.FileSystem.LockFileEx Windows.Win32.Storage.FileSystem.LPPROGRESS_ROUTINE Windows.Win32.Storage.FileSystem.LPPROGRESS_ROUTINE_CALLBACK_REASON Windows.Win32.Storage.FileSystem.MAXIMUM_REPARSE_DATA_BUFFER_SIZE @@ -2394,6 +2397,7 @@ Windows.Win32.Storage.FileSystem.SYMBOLIC_LINK_FLAG_DIRECTORY Windows.Win32.Storage.FileSystem.SYMBOLIC_LINK_FLAGS Windows.Win32.Storage.FileSystem.SYNCHRONIZE Windows.Win32.Storage.FileSystem.TRUNCATE_EXISTING +Windows.Win32.Storage.FileSystem.UnlockFile Windows.Win32.Storage.FileSystem.VOLUME_NAME_DOS Windows.Win32.Storage.FileSystem.VOLUME_NAME_GUID Windows.Win32.Storage.FileSystem.VOLUME_NAME_NONE diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index ab5f8919d7af..f17320cfad58 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -65,6 +65,7 @@ windows_targets::link!("kernel32.dll" "system" fn InitOnceBeginInitialize(lpinit windows_targets::link!("kernel32.dll" "system" fn InitOnceComplete(lpinitonce : *mut INIT_ONCE, dwflags : u32, lpcontext : *const core::ffi::c_void) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn InitializeProcThreadAttributeList(lpattributelist : LPPROC_THREAD_ATTRIBUTE_LIST, dwattributecount : u32, dwflags : u32, lpsize : *mut usize) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn LocalFree(hmem : HLOCAL) -> HLOCAL); +windows_targets::link!("kernel32.dll" "system" fn LockFileEx(hfile : HANDLE, dwflags : LOCK_FILE_FLAGS, dwreserved : u32, nnumberofbytestolocklow : u32, nnumberofbytestolockhigh : u32, lpoverlapped : *mut OVERLAPPED) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn MoveFileExW(lpexistingfilename : PCWSTR, lpnewfilename : PCWSTR, dwflags : MOVE_FILE_FLAGS) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : MULTI_BYTE_TO_WIDE_CHAR_FLAGS, lpmultibytestr : PCSTR, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32); windows_targets::link!("kernel32.dll" "system" fn QueryPerformanceCounter(lpperformancecount : *mut i64) -> BOOL); @@ -96,6 +97,7 @@ windows_targets::link!("kernel32.dll" "system" fn TlsGetValue(dwtlsindex : u32) windows_targets::link!("kernel32.dll" "system" fn TlsSetValue(dwtlsindex : u32, lptlsvalue : *const core::ffi::c_void) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn TryAcquireSRWLockExclusive(srwlock : *mut SRWLOCK) -> BOOLEAN); windows_targets::link!("kernel32.dll" "system" fn TryAcquireSRWLockShared(srwlock : *mut SRWLOCK) -> BOOLEAN); +windows_targets::link!("kernel32.dll" "system" fn UnlockFile(hfile : HANDLE, dwfileoffsetlow : u32, dwfileoffsethigh : u32, nnumberofbytestounlocklow : u32, nnumberofbytestounlockhigh : u32) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn UpdateProcThreadAttribute(lpattributelist : LPPROC_THREAD_ATTRIBUTE_LIST, dwflags : u32, attribute : usize, lpvalue : *const core::ffi::c_void, cbsize : usize, lppreviousvalue : *mut core::ffi::c_void, lpreturnsize : *const usize) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn WaitForMultipleObjects(ncount : u32, lphandles : *const HANDLE, bwaitall : BOOL, dwmilliseconds : u32) -> WAIT_EVENT); windows_targets::link!("kernel32.dll" "system" fn WaitForSingleObject(hhandle : HANDLE, dwmilliseconds : u32) -> WAIT_EVENT); @@ -2725,6 +2727,9 @@ pub struct LINGER { pub l_onoff: u16, pub l_linger: u16, } +pub const LOCKFILE_EXCLUSIVE_LOCK: LOCK_FILE_FLAGS = 2u32; +pub const LOCKFILE_FAIL_IMMEDIATELY: LOCK_FILE_FLAGS = 1u32; +pub type LOCK_FILE_FLAGS = u32; pub type LPOVERLAPPED_COMPLETION_ROUTINE = Option< unsafe extern "system" fn( dwerrorcode: u32, diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index aab471e28eae..bc728d498c3c 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -346,6 +346,94 @@ impl File { self.fsync() } + pub fn lock(&self) -> io::Result<()> { + cvt(unsafe { + let mut overlapped = mem::zeroed(); + c::LockFileEx( + self.handle.as_raw_handle(), + c::LOCKFILE_EXCLUSIVE_LOCK, + 0, + u32::MAX, + u32::MAX, + &mut overlapped, + ) + })?; + Ok(()) + } + + pub fn lock_shared(&self) -> io::Result<()> { + cvt(unsafe { + let mut overlapped = mem::zeroed(); + c::LockFileEx(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX, &mut overlapped) + })?; + Ok(()) + } + + pub fn try_lock(&self) -> io::Result { + let result = cvt(unsafe { + let mut overlapped = mem::zeroed(); + c::LockFileEx( + self.handle.as_raw_handle(), + c::LOCKFILE_EXCLUSIVE_LOCK | c::LOCKFILE_FAIL_IMMEDIATELY, + 0, + u32::MAX, + u32::MAX, + &mut overlapped, + ) + }); + + if let Err(ref err) = result { + if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) + || err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) + { + return Ok(false); + } + } + result?; + Ok(true) + } + + pub fn try_lock_shared(&self) -> io::Result { + let result = cvt(unsafe { + let mut overlapped = mem::zeroed(); + c::LockFileEx( + self.handle.as_raw_handle(), + c::LOCKFILE_FAIL_IMMEDIATELY, + 0, + u32::MAX, + u32::MAX, + &mut overlapped, + ) + }); + + if let Err(ref err) = result { + if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) + || err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) + { + return Ok(false); + } + } + result?; + Ok(true) + } + + pub fn unlock(&self) -> io::Result<()> { + // Unlock the handle twice because LockFileEx() allows a file handle to acquire + // both an exclusive and shared lock, in which case the documentation states that: + // "...two unlock operations are necessary to unlock the region; the first unlock operation + // unlocks the exclusive lock, the second unlock operation unlocks the shared lock" + cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) })?; + let result = + cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) }); + if let Err(ref err) = result { + if err.raw_os_error() == Some(c::ERROR_NOT_LOCKED as i32) { + return Ok(()); + } + } + result?; + Ok(()) + } + pub fn truncate(&self, size: u64) -> io::Result<()> { let info = c::FILE_END_OF_FILE_INFO { EndOfFile: size as i64 }; api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result() From e0fdaa8624ee062b38372c2987947ff2dc6f7113 Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Fri, 18 Oct 2024 21:08:25 -0700 Subject: [PATCH 006/214] Support lock() and lock_shared() on async IO Files --- library/std/src/fs/tests.rs | 38 ++++++++++++++++ library/std/src/sys/pal/windows/fs.rs | 62 +++++++++++++++++++++------ 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index fe3dc4adff0b..05efed6b5dfc 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -283,6 +283,44 @@ fn file_lock_double_unlock() { assert!(check!(f2.try_lock())); } +#[test] +#[cfg(windows)] +fn file_lock_blocking_async() { + use crate::thread::{sleep, spawn}; + const FILE_FLAG_OVERLAPPED: u32 = 0x40000000; + + let tmpdir = tmpdir(); + let filename = &tmpdir.join("file_lock_blocking_async.txt"); + let f1 = check!(File::create(filename)); + let f2 = + check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename)); + + check!(f1.lock()); + + // Ensure that lock() is synchronous when the file is opened for asynchronous IO + let t = spawn(move || { + check!(f2.lock()); + }); + sleep(Duration::from_secs(1)); + assert!(!t.is_finished()); + check!(f1.unlock()); + t.join().unwrap(); + + // Ensure that lock_shared() is synchronous when the file is opened for asynchronous IO + let f2 = + check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename)); + check!(f1.lock()); + + // Ensure that lock() is synchronous when the file is opened for asynchronous IO + let t = spawn(move || { + check!(f2.lock_shared()); + }); + sleep(Duration::from_secs(1)); + assert!(!t.is_finished()); + check!(f1.unlock()); + t.join().unwrap(); +} + #[test] fn file_test_io_seek_shakedown() { // 01234567890123 diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index bc728d498c3c..138ac59c1613 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -346,27 +346,63 @@ impl File { self.fsync() } - pub fn lock(&self) -> io::Result<()> { - cvt(unsafe { - let mut overlapped = mem::zeroed(); - c::LockFileEx( + fn acquire_lock(&self, flags: c::LOCK_FILE_FLAGS) -> io::Result<()> { + unsafe { + let mut overlapped: c::OVERLAPPED = mem::zeroed(); + let event = c::CreateEventW(ptr::null_mut(), c::FALSE, c::FALSE, ptr::null()); + if event.is_null() { + return Err(io::Error::last_os_error()); + } + overlapped.hEvent = event; + let lock_result = cvt(c::LockFileEx( self.handle.as_raw_handle(), - c::LOCKFILE_EXCLUSIVE_LOCK, + flags, 0, u32::MAX, u32::MAX, &mut overlapped, - ) - })?; - Ok(()) + )); + + let final_result = match lock_result { + Ok(_) => Ok(()), + Err(err) => { + if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) { + // Wait for the lock to be acquired. This can happen asynchronously, + // if the file handle was opened for async IO + let wait_result = c::WaitForSingleObject(overlapped.hEvent, c::INFINITE); + if wait_result == c::WAIT_OBJECT_0 { + // Wait completed successfully, get the lock operation status + let mut bytes_transferred = 0; + cvt(c::GetOverlappedResult( + self.handle.as_raw_handle(), + &mut overlapped, + &mut bytes_transferred, + c::TRUE, + )) + .map(|_| ()) + } else if wait_result == c::WAIT_FAILED { + // Wait failed + Err(io::Error::last_os_error()) + } else { + // WAIT_ABANDONED and WAIT_TIMEOUT should not be possible + unreachable!() + } + } else { + Err(err) + } + } + }; + c::CloseHandle(overlapped.hEvent); + final_result + } + } + + pub fn lock(&self) -> io::Result<()> { + self.acquire_lock(c::LOCKFILE_EXCLUSIVE_LOCK) } pub fn lock_shared(&self) -> io::Result<()> { - cvt(unsafe { - let mut overlapped = mem::zeroed(); - c::LockFileEx(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX, &mut overlapped) - })?; - Ok(()) + self.acquire_lock(0) } pub fn try_lock(&self) -> io::Result { From d199a63b1160f3eaab372aa969ab91248ee24315 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 25 Oct 2024 10:55:38 +0200 Subject: [PATCH 007/214] ABI compatibility: remove section on target features --- library/core/src/primitive_docs.rs | 32 ++++++------------------------ 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs index 95fa6c9c9507..f28b65c6f258 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -1743,20 +1743,18 @@ mod prim_ref {} /// alignment, they might be passed in different registers and hence not be ABI-compatible. /// /// ABI compatibility as a concern only arises in code that alters the type of function pointers, -/// code that imports functions via `extern` blocks, and in code that combines `#[target_feature]` -/// with `extern fn`. Altering the type of function pointers is wildly unsafe (as in, a lot more -/// unsafe than even [`transmute_copy`][mem::transmute_copy]), and should only occur in the most -/// exceptional circumstances. Most Rust code just imports functions via `use`. `#[target_feature]` -/// is also used rarely. So, most likely you do not have to worry about ABI compatibility. +/// and code that imports functions via `extern` blocks. Altering the type of function pointers is +/// wildly unsafe (as in, a lot more unsafe than even [`transmute_copy`][mem::transmute_copy]), and +/// should only occur in the most exceptional circumstances. Most Rust code just imports functions +/// via `use`. So, most likely you do not have to worry about ABI compatibility. /// /// But assuming such circumstances, what are the rules? For this section, we are only considering /// the ABI of direct Rust-to-Rust calls, not linking in general -- once functions are imported via /// `extern` blocks, there are more things to consider that we do not go into here. /// /// For two signatures to be considered *ABI-compatible*, they must use a compatible ABI string, -/// must take the same number of arguments, the individual argument types and the return types must -/// be ABI-compatible, and the target feature requirements must be met (see the subsection below for -/// the last point). The ABI string is declared via `extern "ABI" fn(...) -> ...`; note that +/// must take the same number of arguments, and the individual argument types and the return types +/// must be ABI-compatible. The ABI string is declared via `extern "ABI" fn(...) -> ...`; note that /// `fn name(...) -> ...` implicitly uses the `"Rust"` ABI string and `extern fn name(...) -> ...` /// implicitly uses the `"C"` ABI string. /// @@ -1826,24 +1824,6 @@ mod prim_ref {} /// Behavior since transmuting `None::>` to `NonZero` violates the non-zero /// requirement. /// -/// #### Requirements concerning target features -/// -/// Under some conditions, the signature used by the caller and the callee can be ABI-incompatible -/// even if the exact same ABI string and types are being used. As an example, the -/// `std::arch::x86_64::__m256` type has a different `extern "C"` ABI when the `avx` feature is -/// enabled vs when it is not enabled. -/// -/// Therefore, to ensure ABI compatibility when code using different target features is combined -/// (such as via `#[target_feature]`), we further require that one of the following conditions is -/// met: -/// -/// - The function uses the `"Rust"` ABI string (which is the default without `extern`). -/// - Caller and callee are using the exact same set of target features. For the callee we consider -/// the features enabled (via `#[target_feature]` and `-C target-feature`/`-C target-cpu`) at the -/// declaration site; for the caller we consider the features enabled at the call site. -/// - Neither any argument nor the return value involves a SIMD type (`#[repr(simd)]`) that is not -/// behind a pointer indirection (i.e., `*mut __m256` is fine, but `(i32, __m256)` is not). -/// /// ### Trait implementations /// /// In this documentation the shorthand `fn(T₁, T₂, …, Tₙ)` is used to represent non-variadic From ecfcd0879cf9e54992328403655ec4aaeee1a6a9 Mon Sep 17 00:00:00 2001 From: Adrian Taylor Date: Fri, 25 Oct 2024 11:07:06 +0000 Subject: [PATCH 008/214] Arbitrary self types v2: (unused) Receiver trait This commit contains a new Receiver trait, which is the basis for the Arbitrary Self Types v2 RFC. This allows smart pointers to be method receivers even if they're not Deref. This is currently unused by the compiler - a subsequent PR will start to use this for method resolution if the arbitrary_self_types feature gate is enabled. This is being landed first simply to make review simpler: if people feel this should all be in an atomic PR let me know. This is a part of the arbitrary self types v2 project, https://github.com/rust-lang/rfcs/pull/3519 https://github.com/rust-lang/rust/issues/44874 r? @wesleywiser --- compiler/rustc_hir/src/lang_items.rs | 2 + compiler/rustc_span/src/symbol.rs | 2 + library/core/src/ops/deref.rs | 88 +++++++++++++++++++++++++++- library/core/src/ops/mod.rs | 3 + 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index ae046c09ebea..ec1a8e0262ab 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -241,6 +241,8 @@ language_item_table! { DerefMut, sym::deref_mut, deref_mut_trait, Target::Trait, GenericRequirement::Exact(0); DerefPure, sym::deref_pure, deref_pure_trait, Target::Trait, GenericRequirement::Exact(0); DerefTarget, sym::deref_target, deref_target, Target::AssocTy, GenericRequirement::None; + Receiver, sym::receiver, receiver_trait, Target::Trait, GenericRequirement::None; + ReceiverTarget, sym::receiver_target, receiver_target, Target::AssocTy, GenericRequirement::None; LegacyReceiver, sym::legacy_receiver, legacy_receiver_trait, Target::Trait, GenericRequirement::None; Fn, kw::Fn, fn_trait, Target::Trait, GenericRequirement::Exact(1); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index aa05b88ea0c4..aa6477634947 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1574,6 +1574,8 @@ symbols! { readonly, realloc, reason, + receiver, + receiver_target, recursion_limit, reexport_test_harness_main, ref_pat_eat_one_layer_2024, diff --git a/library/core/src/ops/deref.rs b/library/core/src/ops/deref.rs index 49b380e45749..ba7feba86e30 100644 --- a/library/core/src/ops/deref.rs +++ b/library/core/src/ops/deref.rs @@ -294,14 +294,98 @@ unsafe impl DerefPure for &T {} #[unstable(feature = "deref_pure_trait", issue = "87121")] unsafe impl DerefPure for &mut T {} +/// Indicates that a struct can be used as a method receiver. +/// That is, a type can use this type as a type of `self`, like this: +/// ```compile_fail +/// # // This is currently compile_fail because the compiler-side parts +/// # // of arbitrary_self_types are not implemented +/// use std::ops::Receiver; +/// +/// struct SmartPointer(T); +/// +/// impl Receiver for SmartPointer { +/// type Target = T; +/// } +/// +/// struct MyContainedType; +/// +/// impl MyContainedType { +/// fn method(self: SmartPointer) { +/// // ... +/// } +/// } +/// +/// fn main() { +/// let ptr = SmartPointer(MyContainedType); +/// ptr.method(); +/// } +/// ``` +/// This trait is blanket implemented for any type which implements +/// [`Deref`], which includes stdlib pointer types like `Box`,`Rc`, `&T`, +/// and `Pin

`. For that reason, it's relatively rare to need to +/// implement this directly. You'll typically do this only if you need +/// to implement a smart pointer type which can't implement [`Deref`]; perhaps +/// because you're interfacing with another programming language and can't +/// guarantee that references comply with Rust's aliasing rules. +/// +/// When looking for method candidates, Rust will explore a chain of possible +/// `Receiver`s, so for example each of the following methods work: +/// ``` +/// use std::boxed::Box; +/// use std::rc::Rc; +/// +/// // Both `Box` and `Rc` (indirectly) implement Receiver +/// +/// struct MyContainedType; +/// +/// fn main() { +/// let t = Rc::new(Box::new(MyContainedType)); +/// t.method_a(); +/// t.method_b(); +/// t.method_c(); +/// } +/// +/// impl MyContainedType { +/// fn method_a(&self) { +/// +/// } +/// fn method_b(self: &Box) { +/// +/// } +/// fn method_c(self: &Rc>) { +/// +/// } +/// } +/// ``` +#[lang = "receiver"] +#[cfg(not(bootstrap))] +#[unstable(feature = "arbitrary_self_types", issue = "44874")] +pub trait Receiver { + /// The target type on which the method may be called. + #[cfg(not(bootstrap))] + #[rustc_diagnostic_item = "receiver_target"] + #[lang = "receiver_target"] + #[unstable(feature = "arbitrary_self_types", issue = "44874")] + type Target: ?Sized; +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "arbitrary_self_types", issue = "44874")] +impl Receiver for P +where + P: Deref, +{ + type Target = T; +} + /// Indicates that a struct can be used as a method receiver, without the /// `arbitrary_self_types` feature. This is implemented by stdlib pointer types like `Box`, /// `Rc`, `&T`, and `Pin

`. /// /// This trait will shortly be removed and replaced with a more generic /// facility based around the current "arbitrary self types" unstable feature. -/// That new facility will use a replacement trait called `Receiver` which is -/// why this is now named `LegacyReceiver`. +/// That new facility will use the replacement trait above called `Receiver` +/// which is why this is now named `LegacyReceiver`. #[cfg_attr(bootstrap, lang = "receiver")] #[cfg_attr(not(bootstrap), lang = "legacy_receiver")] #[unstable(feature = "legacy_receiver_trait", issue = "none")] diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index c9f47e5daadd..cea1f84f3fd6 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -170,6 +170,9 @@ pub use self::coroutine::{Coroutine, CoroutineState}; pub use self::deref::DerefPure; #[unstable(feature = "legacy_receiver_trait", issue = "none")] pub use self::deref::LegacyReceiver; +#[unstable(feature = "arbitrary_self_types", issue = "44874")] +#[cfg(not(bootstrap))] +pub use self::deref::Receiver; #[stable(feature = "rust1", since = "1.0.0")] pub use self::deref::{Deref, DerefMut}; #[stable(feature = "rust1", since = "1.0.0")] From bb0b2ab6b88aecda885fcbb9388df477b26cc8bb Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 29 Oct 2024 00:00:33 +0000 Subject: [PATCH 009/214] Make sure all expr checking fns start with check_expr --- compiler/rustc_hir_typeck/src/_match.rs | 2 +- compiler/rustc_hir_typeck/src/callee.rs | 2 +- compiler/rustc_hir_typeck/src/check.rs | 2 +- compiler/rustc_hir_typeck/src/expr.rs | 41 ++++++++++--------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 8 ++-- compiler/rustc_hir_typeck/src/op.rs | 4 +- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index 3372cae7a510..20c2dfc01d46 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -15,7 +15,7 @@ use crate::{Diverges, Expectation, FnCtxt, Needs}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[instrument(skip(self), level = "debug", ret)] - pub(crate) fn check_match( + pub(crate) fn check_expr_match( &self, expr: &'tcx hir::Expr<'tcx>, scrut: &'tcx hir::Expr<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index ed56bb9c455c..347736883e5d 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -63,7 +63,7 @@ enum CallStep<'tcx> { } impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - pub(crate) fn check_call( + pub(crate) fn check_expr_call( &self, call_expr: &'tcx hir::Expr<'tcx>, callee_expr: &'tcx hir::Expr<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index 2d8943c61592..569a04f35009 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -137,7 +137,7 @@ pub(super) fn check_fn<'a, 'tcx>( } fcx.is_whole_body.set(true); - fcx.check_return_expr(body.value, false); + fcx.check_return_or_body_tail(body.value, false); // Finalize the return check by taking the LUB of the return types // we saw and assigning it to the expected return type. This isn't diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index d6e5fab610ed..b0ca45fdce6a 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -452,15 +452,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tcx = self.tcx; match expr.kind { - ExprKind::Lit(ref lit) => self.check_lit(lit, expected), - ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected), + ExprKind::Lit(ref lit) => self.check_expr_lit(lit, expected), + ExprKind::Binary(op, lhs, rhs) => self.check_expr_binop(expr, op, lhs, rhs, expected), ExprKind::Assign(lhs, rhs, span) => { self.check_expr_assign(expr, expected, lhs, rhs, span) } ExprKind::AssignOp(op, lhs, rhs) => { - self.check_binop_assign(expr, op, lhs, rhs, expected) + self.check_expr_binop_assign(expr, op, lhs, rhs, expected) } - ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr), + ExprKind::Unary(unop, oprnd) => self.check_expr_unop(unop, oprnd, expected, expr), ExprKind::AddrOf(kind, mutbl, oprnd) => { self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr) } @@ -473,7 +473,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id)); self.check_expr_asm(asm) } - ExprKind::OffsetOf(container, fields) => self.check_offset_of(container, fields, expr), + ExprKind::OffsetOf(container, fields) => { + self.check_expr_offset_of(container, fields, expr) + } ExprKind::Break(destination, ref expr_opt) => { self.check_expr_break(destination, expr_opt.as_deref(), expr) } @@ -492,13 +494,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_loop(body, source, expected, expr) } ExprKind::Match(discrim, arms, match_src) => { - self.check_match(expr, discrim, arms, expected, match_src) + self.check_expr_match(expr, discrim, arms, expected, match_src) } ExprKind::Closure(closure) => self.check_expr_closure(closure, expr.span, expected), - ExprKind::Block(body, _) => self.check_block_with_expected(body, expected), - ExprKind::Call(callee, args) => self.check_call(expr, callee, args, expected), + ExprKind::Block(body, _) => self.check_expr_block(body, expected), + ExprKind::Call(callee, args) => self.check_expr_call(expr, callee, args, expected), ExprKind::MethodCall(segment, receiver, args, _) => { - self.check_method_call(expr, segment, receiver, args, expected) + self.check_expr_method_call(expr, segment, receiver, args, expected) } ExprKind::Cast(e, t) => self.check_expr_cast(e, t, expr), ExprKind::Type(e, t) => { @@ -508,7 +510,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ascribed_ty } ExprKind::If(cond, then_expr, opt_else_expr) => { - self.check_then_else(cond, then_expr, opt_else_expr, expr.span, expected) + self.check_expr_if(cond, then_expr, opt_else_expr, expr.span, expected) } ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected), ExprKind::Array(args) => self.check_expr_array(args, expected, expr), @@ -520,7 +522,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ExprKind::Struct(qpath, fields, ref base_expr) => { self.check_expr_struct(expr, expected, qpath, fields, base_expr) } - ExprKind::Field(base, field) => self.check_field(expr, base, field, expected), + ExprKind::Field(base, field) => self.check_expr_field(expr, base, field, expected), ExprKind::Index(base, idx, brackets_span) => { self.check_expr_index(base, idx, expr, brackets_span) } @@ -529,7 +531,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - fn check_expr_unary( + fn check_expr_unop( &self, unop: hir::UnOp, oprnd: &'tcx hir::Expr<'tcx>, @@ -952,7 +954,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if self.ret_coercion_span.get().is_none() { self.ret_coercion_span.set(Some(e.span)); } - self.check_return_expr(e, true); + self.check_return_or_body_tail(e, true); } else { let mut coercion = self.ret_coercion.as_ref().unwrap().borrow_mut(); if self.ret_coercion_span.get().is_none() { @@ -1015,7 +1017,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// `explicit_return` is `true` if we're checking an explicit `return expr`, /// and `false` if we're checking a trailing expression. - pub(super) fn check_return_expr( + pub(super) fn check_return_or_body_tail( &self, return_expr: &'tcx hir::Expr<'tcx>, explicit_return: bool, @@ -1239,7 +1241,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // A generic function for checking the 'then' and 'else' clauses in an 'if' // or 'if-else' expression. - fn check_then_else( + fn check_expr_if( &self, cond_expr: &'tcx hir::Expr<'tcx>, then_expr: &'tcx hir::Expr<'tcx>, @@ -1522,7 +1524,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// Checks a method call. - fn check_method_call( + fn check_expr_method_call( &self, expr: &'tcx hir::Expr<'tcx>, segment: &'tcx hir::PathSegment<'tcx>, @@ -2574,7 +2576,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Check field access expressions - fn check_field( + fn check_expr_field( &self, expr: &'tcx hir::Expr<'tcx>, base: &'tcx hir::Expr<'tcx>, @@ -3515,8 +3517,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let previous_diverges = self.diverges.get(); // The label blocks should have unit return value or diverge. - let ty = - self.check_block_with_expected(block, ExpectHasType(self.tcx.types.unit)); + let ty = self.check_expr_block(block, ExpectHasType(self.tcx.types.unit)); if !ty.is_never() { self.demand_suptype(block.span, self.tcx.types.unit, ty); diverge = false; @@ -3531,7 +3532,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if diverge { self.tcx.types.never } else { self.tcx.types.unit } } - fn check_offset_of( + fn check_expr_offset_of( &self, container: &'tcx hir::Ty<'tcx>, fields: &[Ident], diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index a6c249da103f..50d1322eba64 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1565,7 +1565,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // AST fragment checking - pub(in super::super) fn check_lit( + pub(in super::super) fn check_expr_lit( &self, lit: &hir::Lit, expected: Expectation<'tcx>, @@ -1747,7 +1747,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(blk) = decl.origin.try_get_else() { let previous_diverges = self.diverges.get(); - let else_ty = self.check_block_with_expected(blk, NoExpectation); + let else_ty = self.check_expr_block(blk, NoExpectation); let cause = self.cause(blk.span, ObligationCauseCode::LetElse); if let Err(err) = self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty) { @@ -1805,7 +1805,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn check_block_no_value(&self, blk: &'tcx hir::Block<'tcx>) { let unit = self.tcx.types.unit; - let ty = self.check_block_with_expected(blk, ExpectHasType(unit)); + let ty = self.check_expr_block(blk, ExpectHasType(unit)); // if the block produces a `!` value, that can always be // (effectively) coerced to unit. @@ -1814,7 +1814,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - pub(in super::super) fn check_block_with_expected( + pub(in super::super) fn check_expr_block( &self, blk: &'tcx hir::Block<'tcx>, expected: Expectation<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 1574e9e98d4d..c1a7340ced92 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -26,7 +26,7 @@ use crate::Expectation; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Checks a `a = b` - pub(crate) fn check_binop_assign( + pub(crate) fn check_expr_binop_assign( &self, expr: &'tcx hir::Expr<'tcx>, op: hir::BinOp, @@ -85,7 +85,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// Checks a potentially overloaded binary operator. - pub(crate) fn check_binop( + pub(crate) fn check_expr_binop( &self, expr: &'tcx hir::Expr<'tcx>, op: hir::BinOp, From 2deb5fdd93b5af1a8b375bc0ce9cb5016cd821fc Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 29 Oct 2024 00:08:34 +0000 Subject: [PATCH 010/214] Stop passing two optional pieces of data disjointly --- compiler/rustc_hir_typeck/src/callee.rs | 3 +-- compiler/rustc_hir_typeck/src/expr.rs | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 347736883e5d..782b4884f2a3 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -75,8 +75,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .check_expr_with_expectation_and_args( callee_expr, Expectation::NoExpectation, - arg_exprs, - Some(call_expr), + Some((call_expr, arg_exprs)), ), _ => self.check_expr(callee_expr), }; diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index b0ca45fdce6a..2fadd192fa0f 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -161,7 +161,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx> { - self.check_expr_with_expectation_and_args(expr, expected, &[], None) + self.check_expr_with_expectation_and_args(expr, expected, None) } /// Same as `check_expr_with_expectation`, but allows us to pass in the arguments of a @@ -170,8 +170,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, - args: &'tcx [hir::Expr<'tcx>], - call: Option<&'tcx hir::Expr<'tcx>>, + call_expr_and_args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, ) -> Ty<'tcx> { if self.tcx().sess.verbose_internals() { // make this code only run with -Zverbose-internals because it is probably slow @@ -216,9 +215,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let ty = ensure_sufficient_stack(|| match &expr.kind { + // Intercept the callee path expr and give it better spans. hir::ExprKind::Path( qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)), - ) => self.check_expr_path(qpath, expr, Some(args), call), + ) => self.check_expr_path(qpath, expr, call_expr_and_args), _ => self.check_expr_kind(expr, expected), }); let ty = self.resolve_vars_if_possible(ty); @@ -467,7 +467,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ExprKind::Path(QPath::LangItem(lang_item, _)) => { self.check_lang_item_path(lang_item, expr) } - ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr, None, None), + ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr, None), ExprKind::InlineAsm(asm) => { // We defer some asm checks as we may not have resolved the input and output types yet (they may still be infer vars). self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id)); @@ -681,8 +681,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, qpath: &'tcx hir::QPath<'tcx>, expr: &'tcx hir::Expr<'tcx>, - args: Option<&'tcx [hir::Expr<'tcx>]>, - call: Option<&'tcx hir::Expr<'tcx>>, + call_expr_and_args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, ) -> Ty<'tcx> { let tcx = self.tcx; let (res, opt_ty, segs) = @@ -712,7 +711,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { segs, opt_ty, res, - call.map_or(expr.span, |e| e.span), + call_expr_and_args.map_or(expr.span, |(e, _)| e.span), expr.span, expr.hir_id, ) @@ -751,7 +750,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We just want to check sizedness, so instead of introducing // placeholder lifetimes with probing, we just replace higher lifetimes // with fresh vars. - let span = args.and_then(|args| args.get(i)).map_or(expr.span, |arg| arg.span); + let span = call_expr_and_args + .and_then(|(_, args)| args.get(i)) + .map_or(expr.span, |arg| arg.span); let input = self.instantiate_binder_with_fresh_vars( span, infer::BoundRegionConversionTime::FnCall, @@ -777,7 +778,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); self.require_type_is_sized_deferred( output, - call.map_or(expr.span, |e| e.span), + call_expr_and_args.map_or(expr.span, |(e, _)| e.span), ObligationCauseCode::SizedCallReturnType, ); } From 86da25051f64d1af51bb142e736bc0ba34e20768 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 29 Oct 2024 00:24:42 +0000 Subject: [PATCH 011/214] Document some check_expr methods --- compiler/rustc_hir_typeck/src/expr.rs | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 2fadd192fa0f..e067ba6408ae 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -55,6 +55,9 @@ use crate::{ }; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + /// Check an expr with an expectation type, and also demand that the expr's + /// evaluated type is a subtype of the expectation at the end. This is a + /// *hard* requirement. pub(crate) fn check_expr_has_type_or_error( &self, expr: &'tcx hir::Expr<'tcx>, @@ -97,6 +100,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty } + /// Check an expr with an expectation type, and also demand that the expr's + /// evaluated type is a coercible to the expectation at the end. This is a + /// *hard* requirement. pub(super) fn check_expr_coercible_to_type( &self, expr: &'tcx hir::Expr<'tcx>, @@ -108,6 +114,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.demand_coerce(expr, ty, expected, expected_ty_expr, AllowTwoPhase::No) } + /// Check an expr with an expectation type. Don't actually enforce that expectation + /// is related to the expr's evaluated type via subtyping or coercion. This is + /// usually called because we want to do that subtype/coerce call manually for better + /// diagnostics. pub(super) fn check_expr_with_hint( &self, expr: &'tcx hir::Expr<'tcx>, @@ -116,6 +126,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_with_expectation(expr, ExpectHasType(expected)) } + /// Check an expr with an expectation type, and also [`Needs`] which will + /// prompt typeck to convert any implicit immutable derefs to mutable derefs. fn check_expr_with_expectation_and_needs( &self, expr: &'tcx hir::Expr<'tcx>, @@ -133,10 +145,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty } + /// Check an expr with no expectations. pub(super) fn check_expr(&self, expr: &'tcx hir::Expr<'tcx>) -> Ty<'tcx> { self.check_expr_with_expectation(expr, NoExpectation) } + /// Check an expr with no expectations, but with [`Needs`] which will + /// prompt typeck to convert any implicit immutable derefs to mutable derefs. pub(super) fn check_expr_with_needs( &self, expr: &'tcx hir::Expr<'tcx>, @@ -145,16 +160,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_with_expectation_and_needs(expr, NoExpectation, needs) } - /// Invariant: - /// If an expression has any sub-expressions that result in a type error, - /// inspecting that expression's type with `ty.references_error()` will return - /// true. Likewise, if an expression is known to diverge, inspecting its - /// type with `ty::type_is_bot` will return true (n.b.: since Rust is - /// strict, _|_ can appear in the type of an expression that does not, - /// itself, diverge: for example, fn() -> _|_.) - /// Note that inspecting a type's structure *directly* may expose the fact - /// that there are actually multiple representations for `Error`, so avoid - /// that when err needs to be handled differently. + /// Check an expr with an expectation type which may be used to eagerly + /// guide inference when evaluating that expr. #[instrument(skip(self, expr), level = "debug")] pub(super) fn check_expr_with_expectation( &self, @@ -164,8 +171,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_with_expectation_and_args(expr, expected, None) } - /// Same as `check_expr_with_expectation`, but allows us to pass in the arguments of a - /// `ExprKind::Call` when evaluating its callee when it is an `ExprKind::Path`. + /// Same as [`Self::check_expr_with_expectation`], but allows us to pass in + /// the arguments of a [`ExprKind::Call`] when evaluating its callee that + /// is an [`ExprKind::Path`]. We use this to refine the spans for certain + /// well-formedness guarantees for the path expr. pub(super) fn check_expr_with_expectation_and_args( &self, expr: &'tcx hir::Expr<'tcx>, From 488d5b0d8e3dd25321615ae702689348574cc77f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 11:01:51 -0700 Subject: [PATCH 012/214] rustdoc-search: add type param names to index --- src/librustdoc/html/render/mod.rs | 1 + src/librustdoc/html/render/search_index.rs | 45 ++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8446235fb188..a17c7626148a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -204,6 +204,7 @@ pub(crate) struct IndexItemFunctionType { inputs: Vec, output: Vec, where_clause: Vec>, + param_names: Vec, } impl IndexItemFunctionType { diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index d1939adc1a50..ee2fc1f44b34 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -646,9 +646,25 @@ pub(crate) fn build_index<'tcx>( full_paths.push((*index, path)); } + let param_names: Vec<(usize, String)> = { + let mut prev = Vec::new(); + let mut result = Vec::new(); + for (index, item) in self.items.iter().enumerate() { + if let Some(ty) = &item.search_type + && let my = + ty.param_names.iter().map(|sym| sym.as_str()).collect::>() + && my != prev + { + result.push((index, my.join(","))); + prev = my; + } + } + result + }; + let has_aliases = !self.aliases.is_empty(); let mut crate_data = - serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?; + serializer.serialize_struct("CrateData", if has_aliases { 13 } else { 12 })?; crate_data.serialize_field("t", &types)?; crate_data.serialize_field("n", &names)?; crate_data.serialize_field("q", &full_paths)?; @@ -660,6 +676,7 @@ pub(crate) fn build_index<'tcx>( crate_data.serialize_field("b", &self.associated_item_disambiguators)?; crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?; crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?; + crate_data.serialize_field("P", ¶m_names)?; if has_aliases { crate_data.serialize_field("a", &self.aliases)?; } @@ -758,7 +775,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( None } }); - let (mut inputs, mut output, where_clause) = match item.kind { + let (mut inputs, mut output, param_names, where_clause) = match item.kind { clean::ForeignFunctionItem(ref f, _) | clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) @@ -771,7 +788,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( inputs.retain(|a| a.id.is_some() || a.generics.is_some()); output.retain(|a| a.id.is_some() || a.generics.is_some()); - Some(IndexItemFunctionType { inputs, output, where_clause }) + Some(IndexItemFunctionType { inputs, output, where_clause, param_names }) } fn get_index_type( @@ -1285,7 +1302,7 @@ fn get_fn_inputs_and_outputs<'tcx>( tcx: TyCtxt<'tcx>, impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, -) -> (Vec, Vec, Vec>) { +) -> (Vec, Vec, Vec, Vec>) { let decl = &func.decl; let mut rgen: FxIndexMap)> = Default::default(); @@ -1331,7 +1348,21 @@ fn get_fn_inputs_and_outputs<'tcx>( let mut ret_types = Vec::new(); simplify_fn_type(self_, generics, &decl.output, tcx, 0, &mut ret_types, &mut rgen, true, cache); - let mut simplified_params = rgen.into_values().collect::>(); - simplified_params.sort_by_key(|(idx, _)| -idx); - (arg_types, ret_types, simplified_params.into_iter().map(|(_idx, traits)| traits).collect()) + let mut simplified_params = rgen.into_iter().collect::>(); + simplified_params.sort_by_key(|(_, (idx, _))| -idx); + ( + arg_types, + ret_types, + simplified_params + .iter() + .map(|(name, (_idx, _traits))| match name { + SimplifiedParam::Symbol(name) => *name, + SimplifiedParam::Anonymous(_) => kw::Empty, + SimplifiedParam::AssociatedType(def_id, name) => { + Symbol::intern(&format!("{}::{}", tcx.item_name(*def_id), name)) + } + }) + .collect(), + simplified_params.into_iter().map(|(_name, (_idx, traits))| traits).collect(), + ) } From 2cc1c0c39a481be5a4498dbee4f89a479dabd9c9 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 11:04:04 -0700 Subject: [PATCH 013/214] rustdoc-search: simplify the checkTypes fast path This reduces code size while still matching the common case for plain, concrete types. --- src/librustdoc/html/static/js/search.js | 37 ++++++++----------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index eed64d024c04..995c2e041941 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -2727,33 +2727,18 @@ class DocSearch { if (unboxingDepth >= UNBOXING_LIMIT) { return false; } - if (row.bindings.size === 0 && elem.bindings.size === 0) { - if (elem.id < 0 && mgens === null) { - return row.id < 0 || checkIfInList( - row.generics, - elem, - whereClause, - mgens, - unboxingDepth + 1, - ); - } - if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 && - typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 && - // special case - elem.id !== this.typeNameIdOfArrayOrSlice - && elem.id !== this.typeNameIdOfTupleOrUnit - && elem.id !== this.typeNameIdOfHof - ) { - return row.id === elem.id || checkIfInList( - row.generics, - elem, - whereClause, - mgens, - unboxingDepth, - ); - } + if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 && + row.generics.length === 0 && elem.generics.length === 0 && + row.bindings.size === 0 && elem.bindings.size === 0 && + // special case + elem.id !== this.typeNameIdOfArrayOrSlice && + elem.id !== this.typeNameIdOfHof && + elem.id !== this.typeNameIdOfTupleOrUnit + ) { + return row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty); + } else { + return unifyFunctionTypes([row], [elem], whereClause, mgens, null, unboxingDepth); } - return unifyFunctionTypes([row], [elem], whereClause, mgens, null, unboxingDepth); }; /** From 5c7e7dfe10acde559ad78a700dfefbfaf0ed8772 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 12:33:09 -0700 Subject: [PATCH 014/214] rustdoc-search: pass original names through AST --- src/librustdoc/html/static/js/search.js | 46 +++--- src/tools/rustdoc-js/tester.js | 1 - tests/rustdoc-gui/search-corrections.goml | 12 +- tests/rustdoc-js-std/parser-bindings.js | 52 +++--- tests/rustdoc-js-std/parser-errors.js | 159 +++++++------------ tests/rustdoc-js-std/parser-filter.js | 27 ++-- tests/rustdoc-js-std/parser-generics.js | 18 +-- tests/rustdoc-js-std/parser-hof.js | 86 ++++------ tests/rustdoc-js-std/parser-ident.js | 37 ++--- tests/rustdoc-js-std/parser-literal.js | 7 +- tests/rustdoc-js-std/parser-paths.js | 31 ++-- tests/rustdoc-js-std/parser-quote.js | 21 +-- tests/rustdoc-js-std/parser-reference.js | 55 +++---- tests/rustdoc-js-std/parser-returned.js | 33 ++-- tests/rustdoc-js-std/parser-separators.js | 24 +-- tests/rustdoc-js-std/parser-slice-array.js | 56 +++---- tests/rustdoc-js-std/parser-tuple.js | 65 +++----- tests/rustdoc-js-std/parser-weird-queries.js | 18 +-- tests/rustdoc-js/non-english-identifier.js | 34 ++-- 19 files changed, 295 insertions(+), 487 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 995c2e041941..a4471f1d885a 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -276,7 +276,7 @@ function getFilteredNextElem(query, parserState, elems, isInGenerics) { // The type filter doesn't count as an element since it's a modifier. const typeFilterElem = elems.pop(); checkExtraTypeFilterCharacters(start, parserState); - parserState.typeFilter = typeFilterElem.name; + parserState.typeFilter = typeFilterElem.normalizedPathLast; parserState.pos += 1; parserState.totalElems -= 1; query.literalSearch = false; @@ -686,7 +686,7 @@ function createQueryElement(query, parserState, name, generics, isInGenerics) { } else if (quadcolon !== null) { throw ["Unexpected ", quadcolon[0]]; } - const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/); + const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x => x.toLowerCase()); // In case we only have something like `

`, there is no name. if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) { @@ -726,7 +726,10 @@ function createQueryElement(query, parserState, name, generics, isInGenerics) { if (gen.name !== null) { gen.bindingName.generics.unshift(gen); } - bindings.set(gen.bindingName.name, gen.bindingName.generics); + bindings.set( + gen.bindingName.name.toLowerCase().replace(/_/g, ""), + gen.bindingName.generics, + ); return false; } return true; @@ -1786,8 +1789,7 @@ class DocSearch { */ function newParsedQuery(userQuery) { return { - original: userQuery, - userQuery: userQuery.toLowerCase(), + userQuery, elems: [], returned: [], // Total number of "top" elements (does not include generics). @@ -1909,7 +1911,7 @@ class DocSearch { genericsElems: 0, typeFilter: null, isInBinding: null, - userQuery: userQuery.toLowerCase(), + userQuery, }; let query = newParsedQuery(userQuery); @@ -2097,7 +2099,7 @@ class DocSearch { */ const sortResults = async(results, isType, preferredCrate) => { const userQuery = parsedQuery.userQuery; - const casedUserQuery = parsedQuery.original; + const normalizedUserQuery = parsedQuery.userQuery.toLowerCase(); const result_list = []; for (const result of results.values()) { result.item = this.searchIndex[result.id]; @@ -2109,15 +2111,15 @@ class DocSearch { let a, b; // sort by exact case-sensitive match - a = (aaa.item.name !== casedUserQuery); - b = (bbb.item.name !== casedUserQuery); + a = (aaa.item.name !== userQuery); + b = (bbb.item.name !== userQuery); if (a !== b) { return a - b; } // sort by exact match with regard to the last word (mismatch goes later) - a = (aaa.word !== userQuery); - b = (bbb.word !== userQuery); + a = (aaa.word !== normalizedUserQuery); + b = (bbb.word !== normalizedUserQuery); if (a !== b) { return a - b; } @@ -3163,21 +3165,25 @@ class DocSearch { if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1 && elem.generics.length === 0 && elem.bindings.size === 0) || elem.typeFilter === TY_GENERIC) { - if (genericSymbols.has(elem.name)) { - elem.id = genericSymbols.get(elem.name); + if (genericSymbols.has(elem.normalizedPathLast)) { + elem.id = genericSymbols.get(elem.normalizedPathLast); } else { elem.id = -(genericSymbols.size + 1); - genericSymbols.set(elem.name, elem.id); + genericSymbols.set(elem.normalizedPathLast, elem.id); } - if (elem.typeFilter === -1 && elem.name.length >= 3) { + if (elem.typeFilter === -1 && elem.normalizedPathLast.length >= 3) { // Silly heuristic to catch if the user probably meant // to not write a generic parameter. We don't use it, // just bring it up. - const maxPartDistance = Math.floor(elem.name.length / 3); + const maxPartDistance = Math.floor(elem.normalizedPathLast.length / 3); let matchDist = maxPartDistance + 1; let matchName = ""; for (const name of this.typeNameIdMap.keys()) { - const dist = editDistance(name, elem.name, maxPartDistance); + const dist = editDistance( + name, + elem.normalizedPathLast, + maxPartDistance, + ); if (dist <= matchDist && dist <= maxPartDistance) { if (dist === matchDist && matchName > name) { continue; @@ -3289,7 +3295,7 @@ class DocSearch { sorted_returned, sorted_others, parsedQuery); - await handleAliases(ret, parsedQuery.original.replace(/"/g, ""), + await handleAliases(ret, parsedQuery.userQuery.replace(/"/g, ""), filterCrates, currentCrate); await Promise.all([ret.others, ret.returned, ret.in_args].map(async list => { const descs = await Promise.all(list.map(result => { @@ -3709,11 +3715,11 @@ async function search(forced) { } // Update document title to maintain a meaningful browser history - searchState.title = "\"" + query.original + "\" Search - Rust"; + searchState.title = "\"" + query.userQuery + "\" Search - Rust"; // Because searching is incremental by character, only the most // recent search query is added to the browser history. - updateSearchHistory(buildUrl(query.original, filterCrates)); + updateSearchHistory(buildUrl(query.userQuery, filterCrates)); await showResults( await docSearch.execQuery(query, filterCrates, window.currentCrate), diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index e162ba033cc5..63cda4111e6a 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -84,7 +84,6 @@ function checkNeededFields(fullPath, expected, error_text, queryName, position) if (fullPath.length === 0) { fieldsToCheck = [ "foundElems", - "original", "returned", "userQuery", "error", diff --git a/tests/rustdoc-gui/search-corrections.goml b/tests/rustdoc-gui/search-corrections.goml index b81b1f382a95..f80675730c41 100644 --- a/tests/rustdoc-gui/search-corrections.goml +++ b/tests/rustdoc-gui/search-corrections.goml @@ -24,7 +24,7 @@ assert-css: (".search-corrections", { }) assert-text: ( ".search-corrections", - "Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." + "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." ) // Corrections do get shown on the "In Return Type" tab. @@ -35,7 +35,7 @@ assert-css: (".search-corrections", { }) assert-text: ( ".search-corrections", - "Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." + "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." ) // Now, explicit return values @@ -52,7 +52,7 @@ assert-css: (".search-corrections", { }) assert-text: ( ".search-corrections", - "Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." + "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." ) // Now, generic correction @@ -69,7 +69,7 @@ assert-css: (".search-corrections", { }) assert-text: ( ".search-corrections", - "Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." + "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." ) // Now, generic correction plus error @@ -86,7 +86,7 @@ assert-css: (".search-corrections", { }) assert-text: ( ".search-corrections", - "Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." + "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." ) go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" @@ -102,5 +102,5 @@ assert-css: (".error", { }) assert-text: ( ".error", - "Query parser error: \"Generic type parameter notablestructwithlongnamr does not accept generic parameters\"." + "Query parser error: \"Generic type parameter NotableStructWithLongNamr does not accept generic parameters\"." ) diff --git a/tests/rustdoc-js-std/parser-bindings.js b/tests/rustdoc-js-std/parser-bindings.js index c4909c6242d7..bd379f139ffa 100644 --- a/tests/rustdoc-js-std/parser-bindings.js +++ b/tests/rustdoc-js-std/parser-bindings.js @@ -3,20 +3,22 @@ const PARSED = [ query: 'A', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", + normalizedPathLast: "a", generics: [], bindings: [ [ 'b', [ { - name: "c", + name: "C", fullPath: ["c"], pathWithoutLast: [], pathLast: "c", + normalizedPathLast: "c", generics: [], typeFilter: -1, }, @@ -27,16 +29,15 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: null, }, { query: 'A', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", @@ -45,7 +46,7 @@ const PARSED = [ [ 'b', [{ - name: "c", + name: "C", fullPath: ["c"], pathWithoutLast: [], pathLast: "c", @@ -58,16 +59,15 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: null, }, { query: 'A', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", @@ -89,16 +89,15 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: null, }, { query: 'A', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", @@ -120,16 +119,15 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: null, }, { query: 'A', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", @@ -160,52 +158,47 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: null, }, { query: 'A', elems: [], foundElems: 0, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: "Cannot write `=` twice in a binding", }, { query: 'A', elems: [], foundElems: 0, - original: 'A', + userQuery: 'A', returned: [], - userQuery: 'a', error: "Unexpected `>` after `=`", }, { query: 'B=C', elems: [], foundElems: 0, - original: 'B=C', + userQuery: 'B=C', returned: [], - userQuery: 'b=c', error: "Type parameter `=` must be within generics list", }, { query: '[B=C]', elems: [], foundElems: 0, - original: '[B=C]', + userQuery: '[B=C]', returned: [], - userQuery: '[b=c]', error: "Type parameter `=` cannot be within slice `[]`", }, { query: 'A=C>', elems: [ { - name: "a", + name: "A", fullPath: ["a"], pathWithoutLast: [], pathLast: "a", @@ -215,7 +208,7 @@ const PARSED = [ 'b', [ { - name: "c", + name: "C", fullPath: ["c"], pathWithoutLast: [], pathLast: "c", @@ -223,7 +216,7 @@ const PARSED = [ typeFilter: -1, }, { - name: "x", + name: "X", fullPath: ["x"], pathWithoutLast: [], pathLast: "x", @@ -237,9 +230,8 @@ const PARSED = [ }, ], foundElems: 1, - original: 'A=C>', + userQuery: 'A=C>', returned: [], - userQuery: 'a=c>', error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-errors.js b/tests/rustdoc-js-std/parser-errors.js index 5ce35bf511d7..068298e72360 100644 --- a/tests/rustdoc-js-std/parser-errors.js +++ b/tests/rustdoc-js-std/parser-errors.js @@ -3,450 +3,400 @@ const PARSED = [ query: '

', elems: [], foundElems: 0, - original: "

", + userQuery: "

", returned: [], - userQuery: "

", error: "Found generics without a path", }, { query: '->

', elems: [], foundElems: 0, - original: "->

", + userQuery: "->

", returned: [], - userQuery: "->

", error: "Found generics without a path", }, { query: '-> *', elems: [], foundElems: 0, - original: "-> *", - returned: [], userQuery: "-> *", + returned: [], error: "Unexpected `*` after ` ` (not a valid identifier)", }, { query: 'a<"P">', elems: [], foundElems: 0, - original: "a<\"P\">", + userQuery: "a<\"P\">", returned: [], - userQuery: "a<\"p\">", error: "Unexpected `\"` in generics", }, { query: '"P" "P"', elems: [], foundElems: 0, - original: "\"P\" \"P\"", + userQuery: "\"P\" \"P\"", returned: [], - userQuery: "\"p\" \"p\"", error: "Cannot have more than one element if you use quotes", }, { query: '"P","P"', elems: [], foundElems: 0, - original: "\"P\",\"P\"", + userQuery: "\"P\",\"P\"", returned: [], - userQuery: "\"p\",\"p\"", error: "Cannot have more than one literal search element", }, { query: "P,\"P\"", elems: [], foundElems: 0, - original: "P,\"P\"", + userQuery: "P,\"P\"", returned: [], - userQuery: "p,\"p\"", error: "Cannot use literal search when there is more than one element", }, { query: '"p" p', elems: [], foundElems: 0, - original: "\"p\" p", - returned: [], userQuery: "\"p\" p", + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: '"p",p', elems: [], foundElems: 0, - original: "\"p\",p", - returned: [], userQuery: "\"p\",p", + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: '"const": p', elems: [], foundElems: 0, - original: "\"const\": p", - returned: [], userQuery: "\"const\": p", + returned: [], error: "Cannot use quotes on type filter", }, { query: "a<:a>", elems: [], foundElems: 0, - original: "a<:a>", - returned: [], userQuery: "a<:a>", + returned: [], error: "Expected type filter before `:`", }, { query: "a<::a>", elems: [], foundElems: 0, - original: "a<::a>", - returned: [], userQuery: "a<::a>", + returned: [], error: "Unexpected `::`: paths cannot start with `::`", }, { query: "(p -> p", elems: [], foundElems: 0, - original: "(p -> p", - returned: [], userQuery: "(p -> p", + returned: [], error: "Unclosed `(`", }, { query: "::a::b", elems: [], foundElems: 0, - original: "::a::b", - returned: [], userQuery: "::a::b", + returned: [], error: "Paths cannot start with `::`", }, { query: " ::a::b", elems: [], foundElems: 0, - original: "::a::b", - returned: [], userQuery: "::a::b", + returned: [], error: "Paths cannot start with `::`", }, { query: "a::::b", elems: [], foundElems: 0, - original: "a::::b", - returned: [], userQuery: "a::::b", + returned: [], error: "Unexpected `::::`", }, { query: "a:: ::b", elems: [], foundElems: 0, - original: "a:: ::b", - returned: [], userQuery: "a:: ::b", + returned: [], error: "Unexpected `:: ::`", }, { query: "a::\t::b", elems: [], foundElems: 0, - original: "a:: ::b", - returned: [], userQuery: "a:: ::b", + returned: [], error: "Unexpected `:: ::`", }, { query: "a::b::", elems: [], foundElems: 0, - original: "a::b::", - returned: [], userQuery: "a::b::", + returned: [], error: "Paths cannot end with `::`", }, { query: ":a", elems: [], foundElems: 0, - original: ":a", - returned: [], userQuery: ":a", + returned: [], error: "Expected type filter before `:`", }, { query: "a,b:", elems: [], foundElems: 0, - original: "a,b:", - returned: [], userQuery: "a,b:", + returned: [], error: "Unexpected `:` (expected path after type filter `b:`)", }, { query: "a (b:", elems: [], foundElems: 0, - original: "a (b:", - returned: [], userQuery: "a (b:", + returned: [], error: "Unclosed `(`", }, { query: "_:", elems: [], foundElems: 0, - original: "_:", - returned: [], userQuery: "_:", + returned: [], error: "Unexpected `_` (not a valid identifier)", }, { query: "ab:", elems: [], foundElems: 0, - original: "ab:", - returned: [], userQuery: "ab:", + returned: [], error: "Unexpected `:` (expected path after type filter `ab:`)", }, { query: "a:b", elems: [], foundElems: 0, - original: "a:b", - returned: [], userQuery: "a:b", + returned: [], error: "Unknown type filter `a`", }, { query: "a-bb", elems: [], foundElems: 0, - original: "a-bb", - returned: [], userQuery: "a-bb", + returned: [], error: "Unexpected `-` (did you mean `->`?)", }, { query: "a>bb", elems: [], foundElems: 0, - original: "a>bb", - returned: [], userQuery: "a>bb", + returned: [], error: "Unexpected `>` (did you mean `->`?)", }, { query: "ab'", elems: [], foundElems: 0, - original: "ab'", - returned: [], userQuery: "ab'", + returned: [], error: "Unexpected `'` after `b` (not a valid identifier)", }, { query: '"p" ', elems: [], foundElems: 0, - original: '"p" ', - returned: [], userQuery: '"p" ', + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: '"p",', elems: [], foundElems: 0, - original: '"p",', - returned: [], userQuery: '"p",', + returned: [], error: "Found generics without a path", }, { query: '"p" a', elems: [], foundElems: 0, - original: '"p" a', - returned: [], userQuery: '"p" a', + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: '"p",a', elems: [], foundElems: 0, - original: '"p",a', - returned: [], userQuery: '"p",a', + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: "a,<", elems: [], foundElems: 0, - original: 'a,<', - returned: [], userQuery: 'a,<', + returned: [], error: 'Found generics without a path', }, { query: "aaaaa<>b", elems: [], foundElems: 0, - original: 'aaaaa<>b', - returned: [], userQuery: 'aaaaa<>b', + returned: [], error: 'Expected `,`, `:` or `->` after `>`, found `b`', }, { query: "fn:aaaaa<>b", elems: [], foundElems: 0, - original: 'fn:aaaaa<>b', - returned: [], userQuery: 'fn:aaaaa<>b', + returned: [], error: 'Expected `,`, `:` or `->` after `>`, found `b`', }, { query: "->a<>b", elems: [], foundElems: 0, - original: '->a<>b', - returned: [], userQuery: '->a<>b', + returned: [], error: 'Expected `,` or `=` after `>`, found `b`', }, { query: "a<->", elems: [], foundElems: 0, - original: 'a<->', - returned: [], userQuery: 'a<->', + returned: [], error: 'Unclosed `<`', }, { query: "a:", elems: [], foundElems: 0, - original: "a:", - returned: [], userQuery: "a:", + returned: [], error: 'Unexpected `<` in type filter (before `:`)', }, { query: "a<>:", elems: [], foundElems: 0, - original: "a<>:", - returned: [], userQuery: "a<>:", + returned: [], error: 'Unexpected `<` in type filter (before `:`)', }, { query: "a,:", elems: [], foundElems: 0, - original: "a,:", - returned: [], userQuery: "a,:", + returned: [], error: 'Expected type filter before `:`', }, { query: "a!:", elems: [], foundElems: 0, - original: "a!:", - returned: [], userQuery: "a!:", + returned: [], error: 'Unexpected `!` in type filter (before `:`)', }, { query: " a<> :", elems: [], foundElems: 0, - original: "a<> :", - returned: [], userQuery: "a<> :", + returned: [], error: 'Expected `,`, `:` or `->` after `>`, found `:`', }, { query: "mod : :", elems: [], foundElems: 0, - original: "mod : :", - returned: [], userQuery: "mod : :", + returned: [], error: 'Unexpected `:` (expected path after type filter `mod:`)', }, { query: "mod: :", elems: [], foundElems: 0, - original: "mod: :", - returned: [], userQuery: "mod: :", + returned: [], error: 'Unexpected `:` (expected path after type filter `mod:`)', }, { query: "a!a", elems: [], foundElems: 0, - original: "a!a", - returned: [], userQuery: "a!a", + returned: [], error: 'Unexpected `!`: it can only be at the end of an ident', }, { query: "a!!", elems: [], foundElems: 0, - original: "a!!", - returned: [], userQuery: "a!!", + returned: [], error: 'Cannot have more than one `!` in an ident', }, { query: "mod:a!", elems: [], foundElems: 0, - original: "mod:a!", - returned: [], userQuery: "mod:a!", + returned: [], error: 'Invalid search type: macro `!` and `mod` both specified', }, { query: "mod:!", elems: [], foundElems: 0, - original: "mod:!", - returned: [], userQuery: "mod:!", + returned: [], error: 'Invalid search type: primitive never type `!` and `mod` both specified', }, { query: "a!::a", elems: [], foundElems: 0, - original: "a!::a", - returned: [], userQuery: "a!::a", + returned: [], error: 'Cannot have associated items in macros', }, { query: "a<", elems: [], foundElems: 0, - original: "a<", - returned: [], userQuery: "a<", + returned: [], error: "Unclosed `<`", }, { @@ -479,9 +429,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "p , y", - returned: [], userQuery: "p , y", + returned: [], error: null, }, { @@ -514,9 +463,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "p", - returned: [], userQuery: "p", + returned: [], error: null, }, { @@ -548,9 +496,8 @@ const PARSED = [ }, ], foundElems: 3, - original: "p ,x , y", - returned: [], userQuery: "p ,x , y", + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-filter.js b/tests/rustdoc-js-std/parser-filter.js index a1dd0ea3b5a9..cda950461f7e 100644 --- a/tests/rustdoc-js-std/parser-filter.js +++ b/tests/rustdoc-js-std/parser-filter.js @@ -10,9 +10,8 @@ const PARSED = [ typeFilter: 7, }], foundElems: 1, - original: "fn:foo", - returned: [], userQuery: "fn:foo", + returned: [], error: null, }, { @@ -26,18 +25,16 @@ const PARSED = [ typeFilter: 6, }], foundElems: 1, - original: "enum : foo", - returned: [], userQuery: "enum : foo", + returned: [], error: null, }, { query: 'macro:foo', elems: [], foundElems: 0, - original: "macro:foo", - returned: [], userQuery: "macro:foo", + returned: [], error: "Unexpected `<` in type filter (before `:`)", }, { @@ -51,9 +48,8 @@ const PARSED = [ typeFilter: 16, }], foundElems: 1, - original: "macro!", - returned: [], userQuery: "macro!", + returned: [], error: null, }, { @@ -67,9 +63,8 @@ const PARSED = [ typeFilter: 16, }], foundElems: 1, - original: "macro:mac!", - returned: [], userQuery: "macro:mac!", + returned: [], error: null, }, { @@ -83,16 +78,15 @@ const PARSED = [ typeFilter: 16, }], foundElems: 1, - original: "a::mac!", - returned: [], userQuery: "a::mac!", + returned: [], error: null, }, { query: '-> fn:foo', elems: [], foundElems: 1, - original: "-> fn:foo", + userQuery: "-> fn:foo", returned: [{ name: "foo", fullPath: ["foo"], @@ -101,14 +95,13 @@ const PARSED = [ generics: [], typeFilter: 7, }], - userQuery: "-> fn:foo", error: null, }, { query: '-> fn:foo', elems: [], foundElems: 1, - original: "-> fn:foo", + userQuery: "-> fn:foo", returned: [{ name: "foo", fullPath: ["foo"], @@ -126,14 +119,13 @@ const PARSED = [ ], typeFilter: 7, }], - userQuery: "-> fn:foo", error: null, }, { query: '-> fn:foo', elems: [], foundElems: 1, - original: "-> fn:foo", + userQuery: "-> fn:foo", returned: [{ name: "foo", fullPath: ["foo"], @@ -159,7 +151,6 @@ const PARSED = [ ], typeFilter: 7, }], - userQuery: "-> fn:foo", error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-generics.js b/tests/rustdoc-js-std/parser-generics.js index 726ee56c2c1b..8b8d95bcb888 100644 --- a/tests/rustdoc-js-std/parser-generics.js +++ b/tests/rustdoc-js-std/parser-generics.js @@ -3,9 +3,8 @@ const PARSED = [ query: 'A, E>', elems: [], foundElems: 0, - original: 'A, E>', + userQuery: 'A, E>', returned: [], - userQuery: 'a, e>', error: 'Unclosed `<`', }, { @@ -29,9 +28,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "p<>,u8", - returned: [], userQuery: "p<>,u8", + returned: [], error: null, }, { @@ -55,9 +53,8 @@ const PARSED = [ }, ], foundElems: 1, - original: '"p"', - returned: [], userQuery: '"p"', + returned: [], error: null, }, { @@ -89,9 +86,8 @@ const PARSED = [ }, ], foundElems: 1, - original: 'p>', - returned: [], userQuery: 'p>', + returned: [], error: null, }, { @@ -130,9 +126,8 @@ const PARSED = [ }, ], foundElems: 1, - original: 'p, r>', - returned: [], userQuery: 'p, r>', + returned: [], error: null, }, { @@ -171,9 +166,8 @@ const PARSED = [ }, ], foundElems: 1, - original: 'p>', - returned: [], userQuery: 'p>', + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-hof.js b/tests/rustdoc-js-std/parser-hof.js index 0b99c45b7a92..ca7610154128 100644 --- a/tests/rustdoc-js-std/parser-hof.js +++ b/tests/rustdoc-js-std/parser-hof.js @@ -12,13 +12,13 @@ const PARSED = [ [ "output", [{ - name: "f", + name: "F", fullPath: ["f"], pathWithoutLast: [], pathLast: "f", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -32,9 +32,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(-> F

)", + userQuery: "(-> F

)", returned: [], - userQuery: "(-> f

)", error: null, }, { @@ -49,7 +48,7 @@ const PARSED = [ [ "output", [{ - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -61,9 +60,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(-> P)", + userQuery: "(-> P)", returned: [], - userQuery: "(-> p)", error: null, }, { @@ -90,9 +88,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(->,a)", - returned: [], userQuery: "(->,a)", + returned: [], error: null, }, { @@ -103,13 +100,13 @@ const PARSED = [ pathWithoutLast: [], pathLast: "->", generics: [{ - name: "f", + name: "F", fullPath: ["f"], pathWithoutLast: [], pathLast: "f", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -127,9 +124,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(F

->)", + userQuery: "(F

->)", returned: [], - userQuery: "(f

->)", error: null, }, { @@ -140,7 +136,7 @@ const PARSED = [ pathWithoutLast: [], pathLast: "->", generics: [{ - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -156,9 +152,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(P ->)", + userQuery: "(P ->)", returned: [], - userQuery: "(p ->)", error: null, }, { @@ -185,9 +180,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(,a->)", - returned: [], userQuery: "(,a->)", + returned: [], error: null, }, { @@ -221,9 +215,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(aaaaa->a)", - returned: [], userQuery: "(aaaaa->a)", + returned: [], error: null, }, { @@ -267,9 +260,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(aaaaa, b -> a)", - returned: [], userQuery: "(aaaaa, b -> a)", + returned: [], error: null, }, { @@ -313,9 +305,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "primitive:(aaaaa, b -> a)", - returned: [], userQuery: "primitive:(aaaaa, b -> a)", + returned: [], error: null, }, { @@ -369,16 +360,15 @@ const PARSED = [ } ], foundElems: 2, - original: "x, trait:(aaaaa, b -> a)", - returned: [], userQuery: "x, trait:(aaaaa, b -> a)", + returned: [], error: null, }, // Rust-style HOF { query: "Fn () -> F

", elems: [{ - name: "fn", + name: "Fn", fullPath: ["fn"], pathWithoutLast: [], pathLast: "fn", @@ -387,13 +377,13 @@ const PARSED = [ [ "output", [{ - name: "f", + name: "F", fullPath: ["f"], pathWithoutLast: [], pathLast: "f", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -407,15 +397,14 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "Fn () -> F

", + userQuery: "Fn () -> F

", returned: [], - userQuery: "fn () -> f

", error: null, }, { query: "FnMut() -> P", elems: [{ - name: "fnmut", + name: "FnMut", fullPath: ["fnmut"], pathWithoutLast: [], pathLast: "fnmut", @@ -424,7 +413,7 @@ const PARSED = [ [ "output", [{ - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -436,15 +425,14 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "FnMut() -> P", + userQuery: "FnMut() -> P", returned: [], - userQuery: "fnmut() -> p", error: null, }, { query: "(FnMut() -> P)", elems: [{ - name: "fnmut", + name: "FnMut", fullPath: ["fnmut"], pathWithoutLast: [], pathLast: "fnmut", @@ -453,7 +441,7 @@ const PARSED = [ [ "output", [{ - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -465,26 +453,25 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "(FnMut() -> P)", + userQuery: "(FnMut() -> P)", returned: [], - userQuery: "(fnmut() -> p)", error: null, }, { query: "Fn(F

)", elems: [{ - name: "fn", + name: "Fn", fullPath: ["fn"], pathWithoutLast: [], pathLast: "fn", generics: [{ - name: "f", + name: "F", fullPath: ["f"], pathWithoutLast: [], pathLast: "f", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -502,9 +489,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "Fn(F

)", + userQuery: "Fn(F

)", returned: [], - userQuery: "fn(f

)", error: null, }, { @@ -548,9 +534,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "primitive:fnonce(aaaaa, b) -> a", - returned: [], userQuery: "primitive:fnonce(aaaaa, b) -> a", + returned: [], error: null, }, { @@ -594,9 +579,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a", - returned: [], userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a", + returned: [], error: null, }, { @@ -665,9 +649,8 @@ const PARSED = [ } ], foundElems: 2, - original: "x, trait:fn(aaaaa, b -> a)", - returned: [], userQuery: "x, trait:fn(aaaaa, b -> a)", + returned: [], error: null, }, { @@ -704,9 +687,8 @@ const PARSED = [ } ], foundElems: 2, - original: "a,b(c)", - returned: [], userQuery: "a,b(c)", + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-ident.js b/tests/rustdoc-js-std/parser-ident.js index cc79c58f1da3..f65391b15718 100644 --- a/tests/rustdoc-js-std/parser-ident.js +++ b/tests/rustdoc-js-std/parser-ident.js @@ -2,7 +2,7 @@ const PARSED = [ { query: "R", elems: [{ - name: "r", + name: "R", fullPath: ["r"], pathWithoutLast: [], pathLast: "r", @@ -19,9 +19,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "R", + userQuery: "R", returned: [], - userQuery: "r", error: null, }, { @@ -35,9 +34,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "!", - returned: [], userQuery: "!", + returned: [], error: null, }, { @@ -51,27 +49,24 @@ const PARSED = [ typeFilter: 16, }], foundElems: 1, - original: "a!", - returned: [], userQuery: "a!", + returned: [], error: null, }, { query: "a!::b", elems: [], foundElems: 0, - original: "a!::b", - returned: [], userQuery: "a!::b", + returned: [], error: "Cannot have associated items in macros", }, { query: "!", elems: [], foundElems: 0, - original: "!", + userQuery: "!", returned: [], - userQuery: "!", error: "Never type `!` does not accept generic parameters", }, { @@ -85,36 +80,32 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "!::b", - returned: [], userQuery: "!::b", + returned: [], error: null, }, { query: "b::!", elems: [], foundElems: 0, - original: "b::!", - returned: [], userQuery: "b::!", + returned: [], error: "Never type `!` is not associated item", }, { query: "!::!", elems: [], foundElems: 0, - original: "!::!", - returned: [], userQuery: "!::!", + returned: [], error: "Never type `!` is not associated item", }, { query: "b::!::c", elems: [], foundElems: 0, - original: "b::!::c", - returned: [], userQuery: "b::!::c", + returned: [], error: "Never type `!` is not associated item", }, { @@ -126,7 +117,7 @@ const PARSED = [ pathLast: "b", generics: [ { - name: "t", + name: "T", fullPath: ["t"], pathWithoutLast: [], pathLast: "t", @@ -137,18 +128,16 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "!::b", + userQuery: "!::b", returned: [], - userQuery: "!::b", error: null, }, { query: "a!::b!", elems: [], foundElems: 0, - original: "a!::b!", - returned: [], userQuery: "a!::b!", + returned: [], error: "Cannot have associated items in macros", }, ]; diff --git a/tests/rustdoc-js-std/parser-literal.js b/tests/rustdoc-js-std/parser-literal.js index 87c06224dbf2..63e07a246a13 100644 --- a/tests/rustdoc-js-std/parser-literal.js +++ b/tests/rustdoc-js-std/parser-literal.js @@ -2,13 +2,13 @@ const PARSED = [ { query: 'R

', elems: [{ - name: "r", + name: "R", fullPath: ["r"], pathWithoutLast: [], pathLast: "r", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -18,9 +18,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "R

", + userQuery: "R

", returned: [], - userQuery: "r

", error: null, } ]; diff --git a/tests/rustdoc-js-std/parser-paths.js b/tests/rustdoc-js-std/parser-paths.js index 774e5d028cc2..bb34e22e518a 100644 --- a/tests/rustdoc-js-std/parser-paths.js +++ b/tests/rustdoc-js-std/parser-paths.js @@ -2,7 +2,7 @@ const PARSED = [ { query: 'A::B', elems: [{ - name: "a::b", + name: "A::B", fullPath: ["a", "b"], pathWithoutLast: ["a"], pathLast: "b", @@ -10,9 +10,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "A::B", + userQuery: "A::B", returned: [], - userQuery: "a::b", error: null, }, { @@ -26,9 +25,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: 'a:: a', - returned: [], userQuery: 'a:: a', + returned: [], error: null, }, { @@ -42,9 +40,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: 'a ::a', - returned: [], userQuery: 'a ::a', + returned: [], error: null, }, { @@ -58,16 +55,15 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: 'a :: a', - returned: [], userQuery: 'a :: a', + returned: [], error: null, }, { query: 'A::B,C', elems: [ { - name: "a::b", + name: "A::B", fullPath: ["a", "b"], pathWithoutLast: ["a"], pathLast: "b", @@ -75,7 +71,7 @@ const PARSED = [ typeFilter: -1, }, { - name: "c", + name: "C", fullPath: ["c"], pathWithoutLast: [], pathLast: "c", @@ -84,16 +80,15 @@ const PARSED = [ }, ], foundElems: 2, - original: 'A::B,C', + userQuery: 'A::B,C', returned: [], - userQuery: 'a::b,c', error: null, }, { query: 'A::B,C', elems: [ { - name: "a::b", + name: "A::B", fullPath: ["a", "b"], pathWithoutLast: ["a"], pathLast: "b", @@ -109,7 +104,7 @@ const PARSED = [ typeFilter: -1, }, { - name: "c", + name: "C", fullPath: ["c"], pathWithoutLast: [], pathLast: "c", @@ -118,9 +113,8 @@ const PARSED = [ }, ], foundElems: 2, - original: 'A::B,C', + userQuery: 'A::B,C', returned: [], - userQuery: 'a::b,c', error: null, }, { @@ -134,9 +128,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "mod::a", - returned: [], userQuery: "mod::a", + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-quote.js b/tests/rustdoc-js-std/parser-quote.js index 731673cf4633..b485047e385e 100644 --- a/tests/rustdoc-js-std/parser-quote.js +++ b/tests/rustdoc-js-std/parser-quote.js @@ -3,7 +3,7 @@ const PARSED = [ query: '-> "p"', elems: [], foundElems: 1, - original: '-> "p"', + userQuery: '-> "p"', returned: [{ name: "p", fullPath: ["p"], @@ -12,7 +12,6 @@ const PARSED = [ generics: [], typeFilter: -1, }], - userQuery: '-> "p"', error: null, }, { @@ -26,54 +25,48 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: '"p",', - returned: [], userQuery: '"p",', + returned: [], error: null, }, { query: '"p" -> a', elems: [], foundElems: 0, - original: '"p" -> a', - returned: [], userQuery: '"p" -> a', + returned: [], error: "Cannot have more than one element if you use quotes", }, { query: '"a" -> "p"', elems: [], foundElems: 0, - original: '"a" -> "p"', - returned: [], userQuery: '"a" -> "p"', + returned: [], error: "Cannot have more than one literal search element", }, { query: '->"-"', elems: [], foundElems: 0, - original: '->"-"', - returned: [], userQuery: '->"-"', + returned: [], error: 'Unexpected `-` in a string element', }, { query: '"a', elems: [], foundElems: 0, - original: '"a', - returned: [], userQuery: '"a', + returned: [], error: 'Unclosed `"`', }, { query: '""', elems: [], foundElems: 0, - original: '""', - returned: [], userQuery: '""', + returned: [], error: 'Cannot have empty string element', }, ]; diff --git a/tests/rustdoc-js-std/parser-reference.js b/tests/rustdoc-js-std/parser-reference.js index 6b1250146bef..0fa07ae98952 100644 --- a/tests/rustdoc-js-std/parser-reference.js +++ b/tests/rustdoc-js-std/parser-reference.js @@ -3,18 +3,16 @@ const PARSED = [ query: '&[', elems: [], foundElems: 0, - original: '&[', - returned: [], userQuery: '&[', + returned: [], error: 'Unclosed `[`', }, { query: '[&', elems: [], foundElems: 0, - original: '[&', - returned: [], userQuery: '[&', + returned: [], error: 'Unclosed `[`', }, { @@ -39,7 +37,7 @@ const PARSED = [ pathLast: "reference", generics: [ { - name: "d", + name: "D", fullPath: ["d"], pathWithoutLast: [], pathLast: "d", @@ -65,9 +63,8 @@ const PARSED = [ }, ], foundElems: 2, - original: '&&&D, []', + userQuery: '&&&D, []', returned: [], - userQuery: '&&&d, []', error: null, }, { @@ -98,7 +95,7 @@ const PARSED = [ pathLast: "[]", generics: [ { - name: "d", + name: "D", fullPath: ["d"], pathWithoutLast: [], pathLast: "d", @@ -119,9 +116,8 @@ const PARSED = [ }, ], foundElems: 1, - original: '&&&[D]', + userQuery: '&&&[D]', returned: [], - userQuery: '&&&[d]', error: null, }, { @@ -137,9 +133,8 @@ const PARSED = [ }, ], foundElems: 1, - original: '&', - returned: [], userQuery: '&', + returned: [], error: null, }, { @@ -164,9 +159,8 @@ const PARSED = [ }, ], foundElems: 1, - original: '&mut', - returned: [], userQuery: '&mut', + returned: [], error: null, }, { @@ -190,9 +184,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "&,u8", - returned: [], userQuery: "&,u8", + returned: [], error: null, }, { @@ -225,9 +218,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "&mut,u8", - returned: [], userQuery: "&mut,u8", + returned: [], error: null, }, { @@ -252,9 +244,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "&u8", - returned: [], userQuery: "&u8", + returned: [], error: null, }, { @@ -288,9 +279,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "&u8", - returned: [], userQuery: "&u8", + returned: [], error: null, }, { @@ -324,9 +314,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "u8<&u8>", - returned: [], userQuery: "u8<&u8>", + returned: [], error: null, }, { @@ -368,9 +357,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "u8<&u8, u8>", - returned: [], userQuery: "u8<&u8, u8>", + returned: [], error: null, }, { @@ -404,9 +392,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "u8<&u8>", - returned: [], userQuery: "u8<&u8>", + returned: [], error: null, }, { @@ -456,9 +443,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "u8<&mut u8, u8>", - returned: [], userQuery: "u8<&mut u8, u8>", + returned: [], error: null, }, { @@ -483,18 +469,16 @@ const PARSED = [ }, ], foundElems: 1, - original: "primitive:&u8", - returned: [], userQuery: "primitive:&u8", + returned: [], error: null, }, { query: 'macro:&u8', elems: [], foundElems: 0, - original: "macro:&u8", - returned: [], userQuery: "macro:&u8", + returned: [], error: "Invalid search type: primitive `&` and `macro` both specified", }, { @@ -519,9 +503,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "¯o:u8", - returned: [], userQuery: "¯o:u8", + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-returned.js b/tests/rustdoc-js-std/parser-returned.js index 8f68209bb966..30ce26a89205 100644 --- a/tests/rustdoc-js-std/parser-returned.js +++ b/tests/rustdoc-js-std/parser-returned.js @@ -3,15 +3,15 @@ const PARSED = [ query: "-> F

", elems: [], foundElems: 1, - original: "-> F

", + userQuery: "-> F

", returned: [{ - name: "f", + name: "F", fullPath: ["f"], pathWithoutLast: [], pathLast: "f", generics: [ { - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", @@ -20,30 +20,28 @@ const PARSED = [ ], typeFilter: -1, }], - userQuery: "-> f

", error: null, }, { query: "-> P", elems: [], foundElems: 1, - original: "-> P", + userQuery: "-> P", returned: [{ - name: "p", + name: "P", fullPath: ["p"], pathWithoutLast: [], pathLast: "p", generics: [], typeFilter: -1, }], - userQuery: "-> p", error: null, }, { query: "->,a", elems: [], foundElems: 1, - original: "->,a", + userQuery: "->,a", returned: [{ name: "a", fullPath: ["a"], @@ -52,7 +50,6 @@ const PARSED = [ generics: [], typeFilter: -1, }], - userQuery: "->,a", error: null, }, { @@ -66,7 +63,7 @@ const PARSED = [ typeFilter: -1, }], foundElems: 2, - original: "aaaaa->a", + userQuery: "aaaaa->a", returned: [{ name: "a", fullPath: ["a"], @@ -75,14 +72,13 @@ const PARSED = [ generics: [], typeFilter: -1, }], - userQuery: "aaaaa->a", error: null, }, { query: "-> !", elems: [], foundElems: 1, - original: "-> !", + userQuery: "-> !", returned: [{ name: "never", fullPath: ["never"], @@ -91,7 +87,6 @@ const PARSED = [ generics: [], typeFilter: 1, }], - userQuery: "-> !", error: null, }, { @@ -105,9 +100,8 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "a->", - returned: [], userQuery: "a->", + returned: [], hasReturnArrow: true, error: null, }, @@ -122,9 +116,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "!->", - returned: [], userQuery: "!->", + returned: [], hasReturnArrow: true, error: null, }, @@ -139,9 +132,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "! ->", - returned: [], userQuery: "! ->", + returned: [], hasReturnArrow: true, error: null, }, @@ -156,9 +148,8 @@ const PARSED = [ typeFilter: 1, }], foundElems: 1, - original: "primitive:!->", - returned: [], userQuery: "primitive:!->", + returned: [], hasReturnArrow: true, error: null, }, diff --git a/tests/rustdoc-js-std/parser-separators.js b/tests/rustdoc-js-std/parser-separators.js index 7f95f61b006b..cf271c80cdce 100644 --- a/tests/rustdoc-js-std/parser-separators.js +++ b/tests/rustdoc-js-std/parser-separators.js @@ -14,9 +14,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "aaaaaa b", - returned: [], userQuery: "aaaaaa b", + returned: [], error: null, }, { @@ -40,9 +39,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "aaaaaa, b", - returned: [], userQuery: "aaaaaa, b", + returned: [], error: null, }, { @@ -58,9 +56,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a b", - returned: [], userQuery: "a b", + returned: [], error: null, }, { @@ -84,9 +81,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "a,b", - returned: [], userQuery: "a,b", + returned: [], error: null, }, { @@ -102,9 +98,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a b", - returned: [], userQuery: "a b", + returned: [], error: null, }, { @@ -128,9 +123,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a", - returned: [], userQuery: "a", + returned: [], error: null, }, { @@ -161,9 +155,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a", - returned: [], userQuery: "a", + returned: [], error: null, }, { @@ -187,9 +180,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a", - returned: [], userQuery: "a", + returned: [], error: null, }, ]; diff --git a/tests/rustdoc-js-std/parser-slice-array.js b/tests/rustdoc-js-std/parser-slice-array.js index 1de52af94e6b..657979455359 100644 --- a/tests/rustdoc-js-std/parser-slice-array.js +++ b/tests/rustdoc-js-std/parser-slice-array.js @@ -3,9 +3,8 @@ const PARSED = [ query: '[[[D, []]]', elems: [], foundElems: 0, - original: '[[[D, []]]', + userQuery: '[[[D, []]]', returned: [], - userQuery: '[[[d, []]]', error: 'Unclosed `[`', }, { @@ -30,7 +29,7 @@ const PARSED = [ pathLast: "[]", generics: [ { - name: "d", + name: "D", fullPath: ["d"], pathWithoutLast: [], pathLast: "d", @@ -56,9 +55,8 @@ const PARSED = [ }, ], foundElems: 1, - original: '[[[D, []]]]', + userQuery: '[[[D, []]]]', returned: [], - userQuery: '[[[d, []]]]', error: null, }, { @@ -82,9 +80,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "[],u8", - returned: [], userQuery: "[],u8", + returned: [], error: null, }, { @@ -109,9 +106,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "[u8]", - returned: [], userQuery: "[u8]", + returned: [], error: null, }, { @@ -144,9 +140,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "[u8,u8]", - returned: [], userQuery: "[u8,u8]", + returned: [], error: null, }, { @@ -180,9 +175,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "[u8]", - returned: [], userQuery: "[u8]", + returned: [], error: null, }, { @@ -198,90 +192,80 @@ const PARSED = [ }, ], foundElems: 1, - original: "[]", - returned: [], userQuery: "[]", + returned: [], error: null, }, { query: '[>', elems: [], foundElems: 0, - original: "[>", - returned: [], userQuery: "[>", + returned: [], error: "Unexpected `>` after `[`", }, { query: '[<', elems: [], foundElems: 0, - original: "[<", - returned: [], userQuery: "[<", + returned: [], error: "Found generics without a path", }, { query: '[a>', elems: [], foundElems: 0, - original: "[a>", - returned: [], userQuery: "[a>", + returned: [], error: "Unexpected `>` after `[`", }, { query: '[a<', elems: [], foundElems: 0, - original: "[a<", - returned: [], userQuery: "[a<", + returned: [], error: "Unclosed `<`", }, { query: '[a', elems: [], foundElems: 0, - original: "[a", - returned: [], userQuery: "[a", + returned: [], error: "Unclosed `[`", }, { query: '[', elems: [], foundElems: 0, - original: "[", - returned: [], userQuery: "[", + returned: [], error: "Unclosed `[`", }, { query: ']', elems: [], foundElems: 0, - original: "]", - returned: [], userQuery: "]", + returned: [], error: "Unexpected `]`", }, { query: '[a', elems: [], foundElems: 0, - original: "[a", - returned: [], userQuery: "[a", + returned: [], error: "Unclosed `[`", }, { query: 'a]', elems: [], foundElems: 0, - original: "a]", - returned: [], userQuery: "a]", + returned: [], error: "Unexpected `]` after `>`", }, { @@ -306,18 +290,16 @@ const PARSED = [ }, ], foundElems: 1, - original: "primitive:[u8]", - returned: [], userQuery: "primitive:[u8]", + returned: [], error: null, }, { query: 'macro:[u8]', elems: [], foundElems: 0, - original: "macro:[u8]", - returned: [], userQuery: "macro:[u8]", + returned: [], error: "Invalid search type: primitive `[]` and `macro` both specified", }, ]; diff --git a/tests/rustdoc-js-std/parser-tuple.js b/tests/rustdoc-js-std/parser-tuple.js index eb16289d3c05..619250683870 100644 --- a/tests/rustdoc-js-std/parser-tuple.js +++ b/tests/rustdoc-js-std/parser-tuple.js @@ -3,9 +3,8 @@ const PARSED = [ query: '(((D, ()))', elems: [], foundElems: 0, - original: '(((D, ()))', + userQuery: '(((D, ()))', returned: [], - userQuery: '(((d, ()))', error: 'Unclosed `(`', }, { @@ -18,7 +17,7 @@ const PARSED = [ pathLast: "()", generics: [ { - name: "d", + name: "D", fullPath: ["d"], pathWithoutLast: [], pathLast: "d", @@ -38,9 +37,8 @@ const PARSED = [ } ], foundElems: 1, - original: '(((D, ())))', + userQuery: '(((D, ())))', returned: [], - userQuery: '(((d, ())))', error: null, }, { @@ -64,9 +62,8 @@ const PARSED = [ }, ], foundElems: 2, - original: "(),u8", - returned: [], userQuery: "(),u8", + returned: [], error: null, }, // Parens act as grouping operators when: @@ -88,9 +85,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(u8)", - returned: [], userQuery: "(u8)", + returned: [], error: null, }, { @@ -115,9 +111,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(u8,)", - returned: [], userQuery: "(u8,)", + returned: [], error: null, }, { @@ -142,9 +137,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(,u8)", - returned: [], userQuery: "(,u8)", + returned: [], error: null, }, { @@ -169,9 +163,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "primitive:(u8)", - returned: [], userQuery: "primitive:(u8)", + returned: [], error: null, }, { @@ -187,9 +180,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(primitive:u8)", - returned: [], userQuery: "(primitive:u8)", + returned: [], error: null, }, { @@ -222,9 +214,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(u8,u8)", - returned: [], userQuery: "(u8,u8)", + returned: [], error: null, }, { @@ -249,9 +240,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "(u8)", - returned: [], userQuery: "(u8)", + returned: [], error: null, }, { @@ -267,99 +257,88 @@ const PARSED = [ }, ], foundElems: 1, - original: "()", - returned: [], userQuery: "()", + returned: [], error: null, }, { query: '(>', elems: [], foundElems: 0, - original: "(>", - returned: [], userQuery: "(>", + returned: [], error: "Unexpected `>` after `(`", }, { query: '(<', elems: [], foundElems: 0, - original: "(<", - returned: [], userQuery: "(<", + returned: [], error: "Found generics without a path", }, { query: '(a>', elems: [], foundElems: 0, - original: "(a>", - returned: [], userQuery: "(a>", + returned: [], error: "Unexpected `>` after `(`", }, { query: '(a<', elems: [], foundElems: 0, - original: "(a<", - returned: [], userQuery: "(a<", + returned: [], error: "Unclosed `<`", }, { query: '(a', elems: [], foundElems: 0, - original: "(a", - returned: [], userQuery: "(a", + returned: [], error: "Unclosed `(`", }, { query: '(', elems: [], foundElems: 0, - original: "(", - returned: [], userQuery: "(", + returned: [], error: "Unclosed `(`", }, { query: ')', elems: [], foundElems: 0, - original: ")", - returned: [], userQuery: ")", + returned: [], error: "Unexpected `)`", }, { query: '(a', elems: [], foundElems: 0, - original: "(a", - returned: [], userQuery: "(a", + returned: [], error: "Unclosed `(`", }, { query: 'a)', elems: [], foundElems: 0, - original: "a)", - returned: [], userQuery: "a)", + returned: [], error: "Unexpected `)` after `>`", }, { query: 'macro:(u8)', elems: [], foundElems: 0, - original: "macro:(u8)", - returned: [], userQuery: "macro:(u8)", + returned: [], error: "Invalid search type: primitive `()` and `macro` both specified", }, ]; diff --git a/tests/rustdoc-js-std/parser-weird-queries.js b/tests/rustdoc-js-std/parser-weird-queries.js index 499b82a34694..828b0a7d9f66 100644 --- a/tests/rustdoc-js-std/parser-weird-queries.js +++ b/tests/rustdoc-js-std/parser-weird-queries.js @@ -15,9 +15,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a b", - returned: [], userQuery: "a b", + returned: [], error: null, }, { @@ -32,9 +31,8 @@ const PARSED = [ }, ], foundElems: 1, - original: "a b", - returned: [], userQuery: "a b", + returned: [], error: null, }, { @@ -56,36 +54,32 @@ const PARSED = [ }, ], foundElems: 2, - original: "aaa,a", - returned: [], userQuery: "aaa,a", + returned: [], error: null, }, { query: ',,,,', elems: [], foundElems: 0, - original: ",,,,", - returned: [], userQuery: ",,,,", + returned: [], error: null, }, { query: 'mod :', elems: [], foundElems: 0, - original: 'mod :', - returned: [], userQuery: 'mod :', + returned: [], error: "Unexpected `:` (expected path after type filter `mod:`)", }, { query: 'mod\t:', elems: [], foundElems: 0, - original: 'mod :', - returned: [], userQuery: 'mod :', + returned: [], error: "Unexpected `:` (expected path after type filter `mod:`)", }, ]; diff --git a/tests/rustdoc-js/non-english-identifier.js b/tests/rustdoc-js/non-english-identifier.js index 1765a69152a8..6a2c4cc637c9 100644 --- a/tests/rustdoc-js/non-english-identifier.js +++ b/tests/rustdoc-js/non-english-identifier.js @@ -11,30 +11,29 @@ const PARSED = [ }], returned: [], foundElems: 1, - original: "中文", userQuery: "中文", error: null, }, { query: '_0Mixed中英文', elems: [{ - name: "_0mixed中英文", + name: "_0Mixed中英文", fullPath: ["_0mixed中英文"], pathWithoutLast: [], pathLast: "_0mixed中英文", + normalizedPathLast: "0mixed中英文", generics: [], typeFilter: -1, }], foundElems: 1, - original: "_0Mixed中英文", + userQuery: "_0Mixed中英文", returned: [], - userQuery: "_0mixed中英文", error: null, }, { query: 'my_crate::中文API', elems: [{ - name: "my_crate::中文api", + name: "my_crate::中文API", fullPath: ["my_crate", "中文api"], pathWithoutLast: ["my_crate"], pathLast: "中文api", @@ -42,26 +41,25 @@ const PARSED = [ typeFilter: -1, }], foundElems: 1, - original: "my_crate::中文API", + userQuery: "my_crate::中文API", returned: [], - userQuery: "my_crate::中文api", error: null, }, { query: '类型A,类型B<约束C>->返回类型<关联类型=路径::约束D>', elems: [{ - name: "类型a", + name: "类型A", fullPath: ["类型a"], pathWithoutLast: [], pathLast: "类型a", generics: [], }, { - name: "类型b", + name: "类型B", fullPath: ["类型b"], pathWithoutLast: [], pathLast: "类型b", generics: [{ - name: "约束c", + name: "约束C", fullPath: ["约束c"], pathWithoutLast: [], pathLast: "约束c", @@ -71,15 +69,21 @@ const PARSED = [ foundElems: 3, totalElems: 5, literalSearch: true, - original: "类型A,类型B<约束C>->返回类型<关联类型=路径::约束D>", + userQuery: "类型A,类型B<约束C>->返回类型<关联类型=路径::约束D>", returned: [{ name: "返回类型", fullPath: ["返回类型"], pathWithoutLast: [], pathLast: "返回类型", generics: [], + bindings: [["关联类型", [{ + name: "路径::约束D", + fullPath: ["路径", "约束d"], + pathWithoutLast: ["路径"], + pathLast: "约束d", + generics: [], + }]]], }], - userQuery: "类型a,类型b<约束c>->返回类型<关联类型=路径::约束d>", error: null, }, { @@ -93,18 +97,16 @@ const PARSED = [ typeFilter: 16, }], foundElems: 1, - original: "my_crate 中文宏!", - returned: [], userQuery: "my_crate 中文宏!", + returned: [], error: null, }, { query: '非法符号——', elems: [], foundElems: 0, - original: "非法符号——", - returned: [], userQuery: "非法符号——", + returned: [], error: "Unexpected `—` after `号` (not a valid identifier)", } ] From 5973005d93c182fe38c79449ec87e968dc23de62 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 12:36:05 -0700 Subject: [PATCH 015/214] rustdoc-search: use correct type annotations --- src/librustdoc/html/static/js/externs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index 3d3e0a8f8381..fe66c8536b71 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -9,12 +9,12 @@ function initSearch(searchIndex){} /** * @typedef {{ * name: string, - * id: integer|null, + * id: number|null, * fullPath: Array, * pathWithoutLast: Array, * pathLast: string, * generics: Array, - * bindings: Map>, + * bindings: Map>, * }} */ let QueryElement; From 20a4b4fea1e5f3005973ae1391b039722d207119 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 14:31:44 -0700 Subject: [PATCH 016/214] rustdoc-search: show types signatures in results --- src/librustdoc/html/static/css/rustdoc.css | 21 +- src/librustdoc/html/static/js/externs.js | 3 + src/librustdoc/html/static/js/search.js | 688 ++++++++++++++++-- src/tools/rustdoc-js/tester.js | 54 +- .../rustdoc-gui/search-about-this-result.goml | 42 ++ .../rustdoc-js-std/option-type-signatures.js | 174 ++++- tests/rustdoc-js/assoc-type-unbound.js | 39 + tests/rustdoc-js/assoc-type-unbound.rs | 4 + tests/rustdoc-js/assoc-type.js | 48 +- tests/rustdoc-js/generics-trait.js | 48 +- 10 files changed, 995 insertions(+), 126 deletions(-) create mode 100644 tests/rustdoc-gui/search-about-this-result.goml create mode 100644 tests/rustdoc-js/assoc-type-unbound.js create mode 100644 tests/rustdoc-js/assoc-type-unbound.rs diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 1042d254749e..66a8a1989288 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -264,6 +264,7 @@ a.anchor, .mobile-topbar h2 a, h1 a, .search-results a, +.search-results li, .stab, .result-name i { color: var(--main-color); @@ -379,7 +380,7 @@ details:not(.toggle) summary { margin-bottom: .6em; } -code, pre, .code-header { +code, pre, .code-header, .type-signature { font-family: "Source Code Pro", monospace; } .docblock code, .docblock-short code { @@ -1205,22 +1206,28 @@ so that we can apply CSS-filters to change the arrow color in themes */ .search-results.active { display: block; + margin: 0; + padding: 0; } .search-results > a { - display: flex; + display: grid; + grid-template-areas: + "search-result-name search-result-desc" + "search-result-type-signature search-result-type-signature"; + grid-template-columns: .6fr .4fr; /* A little margin ensures the browser's outlining of focused links has room to display. */ margin-left: 2px; margin-right: 2px; border-bottom: 1px solid var(--search-result-border-color); - gap: 1em; + column-gap: 1em; } .search-results > a > div.desc { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - flex: 2; + grid-area: search-result-desc; } .search-results a:hover, @@ -1232,7 +1239,7 @@ so that we can apply CSS-filters to change the arrow color in themes */ display: flex; align-items: center; justify-content: start; - flex: 3; + grid-area: search-result-name; } .search-results .result-name .alias { color: var(--search-results-alias-color); @@ -1253,6 +1260,10 @@ so that we can apply CSS-filters to change the arrow color in themes */ .search-results .result-name .path > * { display: inline; } +.search-results .type-signature { + grid-area: search-result-type-signature; + white-space: pre-wrap; +} .popover { position: absolute; diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index fe66c8536b71..c4faca1c0c3b 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -92,6 +92,9 @@ let Results; * parent: (Object|undefined), * path: string, * ty: number, + * type: FunctionSearchType?, + * displayType: Promise>>|null, + * displayTypeMappedNames: Promise]>>|null, * }} */ let ResultObject; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index a4471f1d885a..0458b81d3525 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -15,7 +15,16 @@ if (!Array.prototype.toSpliced) { }; } -(function() { +function onEachBtwn(arr, func, funcBtwn) { + let skipped = true; + for (const value of arr) { + if (!skipped) { + funcBtwn(value); + } + skipped = func(value); + } +} + // ==================== Core search logic begin ==================== // This mapping table should match the discriminants of // `rustdoc::formats::item_type::ItemType` type in Rust. @@ -50,8 +59,10 @@ const itemTypes = [ ]; // used for special search precedence +const TY_PRIMITIVE = itemTypes.indexOf("primitive"); const TY_GENERIC = itemTypes.indexOf("generic"); const TY_IMPORT = itemTypes.indexOf("import"); +const TY_TRAIT = itemTypes.indexOf("trait"); const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../"; // Hard limit on how deep to recurse into generics when doing type-driven search. @@ -1117,6 +1128,13 @@ class DocSearch { * @type {Map} */ this.typeNameIdMap = new Map(); + /** + * Map from type ID to associated type name. Used for display, + * not for search. + * + * @type {Map} + */ + this.assocTypeIdNameMap = new Map(); this.ALIASES = new Map(); this.rootPath = rootPath; this.searchState = searchState; @@ -1161,6 +1179,14 @@ class DocSearch { * Special type name IDs for searching higher order functions (`->` syntax). */ this.typeNameIdOfHof = this.buildTypeMapIndex("->"); + /** + * Special type name IDs the output assoc type. + */ + this.typeNameIdOfOutput = this.buildTypeMapIndex("output", true); + /** + * Special type name IDs for searching by reference. + */ + this.typeNameIdOfReference = this.buildTypeMapIndex("reference"); /** * Empty, immutable map used in item search types with no bindings. @@ -1237,9 +1263,9 @@ class DocSearch { * * @return {Array} */ - buildItemSearchTypeAll(types, lowercasePaths) { + buildItemSearchTypeAll(types, paths, lowercasePaths) { return types.length > 0 ? - types.map(type => this.buildItemSearchType(type, lowercasePaths)) : + types.map(type => this.buildItemSearchType(type, paths, lowercasePaths)) : this.EMPTY_GENERICS_ARRAY; } @@ -1248,7 +1274,7 @@ class DocSearch { * * @param {RawFunctionType} type */ - buildItemSearchType(type, lowercasePaths, isAssocType) { + buildItemSearchType(type, paths, lowercasePaths, isAssocType) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; const BINDINGS_DATA = 2; @@ -1261,6 +1287,7 @@ class DocSearch { pathIndex = type[PATH_INDEX_DATA]; generics = this.buildItemSearchTypeAll( type[GENERICS_DATA], + paths, lowercasePaths, ); if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) { @@ -1277,8 +1304,8 @@ class DocSearch { // // As a result, the key should never have generics on it. return [ - this.buildItemSearchType(assocType, lowercasePaths, true).id, - this.buildItemSearchTypeAll(constraints, lowercasePaths), + this.buildItemSearchType(assocType, paths, lowercasePaths, true).id, + this.buildItemSearchTypeAll(constraints, paths, lowercasePaths), ]; })); } else { @@ -1294,6 +1321,7 @@ class DocSearch { // the actual names of generic parameters aren't stored, since they aren't API result = { id: pathIndex, + name: "", ty: TY_GENERIC, path: null, exactPath: null, @@ -1304,6 +1332,7 @@ class DocSearch { // `0` is used as a sentinel because it's fewer bytes than `null` result = { id: null, + name: "", ty: null, path: null, exactPath: null, @@ -1312,8 +1341,13 @@ class DocSearch { }; } else { const item = lowercasePaths[pathIndex - 1]; + const id = this.buildTypeMapIndex(item.name, isAssocType); + if (isAssocType) { + this.assocTypeIdNameMap.set(id, paths[pathIndex - 1].name); + } result = { - id: this.buildTypeMapIndex(item.name, isAssocType), + id, + name: paths[pathIndex - 1].name, ty: item.ty, path: item.path, exactPath: item.exactPath, @@ -1355,7 +1389,7 @@ class DocSearch { } if (cr.ty === result.ty && cr.path === result.path && cr.bindings === result.bindings && cr.generics === result.generics - && cr.ty === result.ty + && cr.ty === result.ty && cr.name === result.name ) { return cr; } @@ -1466,11 +1500,12 @@ class DocSearch { * The raw function search type format is generated using serde in * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string * + * @param {Array<{name: string, ty: number}>} paths * @param {Array<{name: string, ty: number}>} lowercasePaths * * @return {null|FunctionSearchType} */ - const buildFunctionSearchTypeCallback = lowercasePaths => { + const buildFunctionSearchTypeCallback = (paths, lowercasePaths) => { return functionSearchType => { if (functionSearchType === 0) { return null; @@ -1480,11 +1515,16 @@ class DocSearch { let inputs, output; if (typeof functionSearchType[INPUTS_DATA] === "number") { inputs = [ - this.buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths), + this.buildItemSearchType( + functionSearchType[INPUTS_DATA], + paths, + lowercasePaths, + ), ]; } else { inputs = this.buildItemSearchTypeAll( functionSearchType[INPUTS_DATA], + paths, lowercasePaths, ); } @@ -1493,12 +1533,14 @@ class DocSearch { output = [ this.buildItemSearchType( functionSearchType[OUTPUT_DATA], + paths, lowercasePaths, ), ]; } else { output = this.buildItemSearchTypeAll( functionSearchType[OUTPUT_DATA], + paths, lowercasePaths, ); } @@ -1509,8 +1551,12 @@ class DocSearch { const l = functionSearchType.length; for (let i = 2; i < l; ++i) { where_clause.push(typeof functionSearchType[i] === "number" - ? [this.buildItemSearchType(functionSearchType[i], lowercasePaths)] - : this.buildItemSearchTypeAll(functionSearchType[i], lowercasePaths)); + ? [this.buildItemSearchType(functionSearchType[i], paths, lowercasePaths)] + : this.buildItemSearchTypeAll( + functionSearchType[i], + paths, + lowercasePaths, + )); } return { inputs, output, where_clause, @@ -1554,6 +1600,13 @@ class DocSearch { this.searchIndexEmptyDesc.set(crate, new RoaringBitmap(crateCorpus.e)); let descIndex = 0; + /** + * List of generic function type parameter names. + * Used for display, not for searching. + * @type {[string]} + */ + let lastParamNames = []; + // This object should have exactly the same set of fields as the "row" // object defined below. Your JavaScript runtime will thank you. // https://mathiasbynens.be/notes/shapes-ics @@ -1568,6 +1621,7 @@ class DocSearch { desc: crateCorpus.doc, parent: undefined, type: null, + paramNames: lastParamNames, id, word: crate, normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), @@ -1604,6 +1658,10 @@ class DocSearch { // an array of [(String) alias name // [Number] index to items] const aliases = crateCorpus.a; + // an array of [(Number) item index, + // (String) comma-separated list of function generic param names] + // an item whose index is not present will fall back to the previous present path + const itemParamNames = new Map(crateCorpus.P); // an array of [{name: String, ty: Number}] const lowercasePaths = []; @@ -1611,7 +1669,7 @@ class DocSearch { // a string representing the list of function types const itemFunctionDecoder = new VlqHexDecoder( crateCorpus.f, - buildFunctionSearchTypeCallback(lowercasePaths), + buildFunctionSearchTypeCallback(paths, lowercasePaths), ); // convert `rawPaths` entries into object form @@ -1662,6 +1720,9 @@ class DocSearch { const name = itemNames[i] === "" ? lastName : itemNames[i]; const word = itemNames[i] === "" ? lastWord : itemNames[i].toLowerCase(); const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath; + const paramNames = itemParamNames.has(i) ? + itemParamNames.get(i).split(",") : + lastParamNames; const type = itemFunctionDecoder.next(); if (type !== null) { if (type) { @@ -1694,6 +1755,7 @@ class DocSearch { itemPaths.get(itemReexports.get(i)) : path, parent: itemParentIdx > 0 ? paths[itemParentIdx - 1] : undefined, type, + paramNames, id, word, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), @@ -1704,6 +1766,7 @@ class DocSearch { id += 1; searchIndex.push(row); lastPath = row.path; + lastParamNames = row.paramNames; if (!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) { descIndex += 1; } @@ -2048,18 +2111,21 @@ class DocSearch { * marked for removal. * * @param {[ResultObject]} results + * @param {"sig"|"elems"|"returned"|null} typeInfo + * @param {ParsedQuery} query * @returns {[ResultObject]} */ - const transformResults = results => { + const transformResults = (results, typeInfo) => { const duplicates = new Set(); const out = []; for (const result of results) { if (result.id !== -1) { - const obj = this.searchIndex[result.id]; - obj.dist = result.dist; - const res = buildHrefAndPath(obj); - obj.displayPath = pathSplitter(res[0]); + const res = buildHrefAndPath(this.searchIndex[result.id]); + const obj = Object.assign({ + dist: result.dist, + displayPath: pathSplitter(res[0]), + }, this.searchIndex[result.id]); // To be sure than it some items aren't considered as duplicate. obj.fullPath = res[2] + "|" + obj.ty; @@ -2078,6 +2144,11 @@ class DocSearch { duplicates.add(obj.fullPath); duplicates.add(res[2]); + if (typeInfo !== null) { + obj.displayTypeSignature = + this.formatDisplayTypeSignature(obj, typeInfo); + } + obj.href = res[1]; out.push(obj); if (out.length >= MAX_RESULTS) { @@ -2088,6 +2159,327 @@ class DocSearch { return out; }; + /** + * Add extra data to result objects, and filter items that have been + * marked for removal. + * + * The output is formatted as an array of hunks, where odd numbered + * hunks are highlighted and even numbered ones are not. + * + * @param {ResultObject} obj + * @param {"sig"|"elems"|"returned"|null} typeInfo + * @param {ParsedQuery} query + * @returns Promise< + * "type": Array, + * "mappedNames": Map, + * "whereClause": Map>, + * > + */ + this.formatDisplayTypeSignature = async(obj, typeInfo) => { + let fnInputs = null; + let fnOutput = null; + let mgens = null; + if (typeInfo !== "elems" && typeInfo !== "returned") { + fnInputs = unifyFunctionTypes( + obj.type.inputs, + parsedQuery.elems, + obj.type.where_clause, + null, + mgensScratch => { + fnOutput = unifyFunctionTypes( + obj.type.output, + parsedQuery.returned, + obj.type.where_clause, + mgensScratch, + mgensOut => { + mgens = mgensOut; + return true; + }, + 0, + ); + return !!fnOutput; + }, + 0, + ); + } else { + const arr = typeInfo === "elems" ? obj.type.inputs : obj.type.output; + const highlighted = unifyFunctionTypes( + arr, + parsedQuery.elems, + obj.type.where_clause, + null, + mgensOut => { + mgens = mgensOut; + return true; + }, + 0, + ); + if (typeInfo === "elems") { + fnInputs = highlighted; + } else { + fnOutput = highlighted; + } + } + if (!fnInputs) { + fnInputs = obj.type.inputs; + } + if (!fnOutput) { + fnOutput = obj.type.output; + } + const mappedNames = new Map(); + const whereClause = new Map(); + + const fnParamNames = obj.paramNames; + const queryParamNames = []; + /** + * Recursively writes a map of IDs to query generic names, + * which are later used to map query generic names to function generic names. + * For example, when the user writes `X -> Option` and the function + * is actually written as `T -> Option`, this function stores the + * mapping `(-1, "X")`, and the writeFn function looks up the entry + * for -1 to form the final, user-visible mapping of "X is T". + * + * @param {QueryElement} queryElem + */ + const remapQuery = queryElem => { + if (queryElem.id < 0) { + queryParamNames[-1 - queryElem.id] = queryElem.name; + } + if (queryElem.generics.length > 0) { + queryElem.generics.forEach(remapQuery); + } + if (queryElem.bindings.size > 0) { + [...queryElem.bindings.values()].flat().forEach(remapQuery); + } + }; + + parsedQuery.elems.forEach(remapQuery); + parsedQuery.returned.forEach(remapQuery); + + /** + * Write text to a highlighting array. + * Index 0 is not highlighted, index 1 is highlighted, + * index 2 is not highlighted, etc. + * + * @param {{name: string, highlighted: bool|undefined}} fnType - input + * @param {[string]} result + */ + const pushText = (fnType, result) => { + // If !!(result.length % 2) == false, then pushing a new slot starts an even + // numbered slot. Even numbered slots are not highlighted. + // + // `highlighted` will not be defined if an entire subtree is not highlighted, + // so `!!` is used to coerce it to boolean. `result.length % 2` is used to + // check if the number is even, but it evaluates to a number, so it also + // needs coerced to a boolean. + if (!!(result.length % 2) === !!fnType.highlighted) { + result.push(""); + } else if (result.length === 0 && !!fnType.highlighted) { + result.push(""); + result.push(""); + } + + result[result.length - 1] += fnType.name; + }; + + /** + * Write a higher order function type: either a function pointer + * or a trait bound on Fn, FnMut, or FnOnce. + * + * @param {FunctionType} fnType - input + * @param {[string]} result + */ + const writeHof = (fnType, result) => { + const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || []; + const hofInputs = fnType.generics; + pushText(fnType, result); + pushText({name: " (", highlighted: false}, result); + let needsComma = false; + for (const fnType of hofInputs) { + if (needsComma) { + pushText({ name: ", ", highlighted: false }, result); + } + needsComma = true; + writeFn(fnType, result); + } + pushText({ + name: hofOutput.length === 0 ? ")" : ") -> ", + highlighted: false, + }, result); + if (hofOutput.length > 1) { + pushText({name: "(", highlighted: false}, result); + } + needsComma = false; + for (const fnType of hofOutput) { + if (needsComma) { + pushText({ name: ", ", highlighted: false }, result); + } + needsComma = true; + writeFn(fnType, result); + } + if (hofOutput.length > 1) { + pushText({name: ")", highlighted: false}, result); + } + }; + + /** + * Write a primitive type with special syntax, like `!` or `[T]`. + * Returns `false` if the supplied type isn't special. + * + * @param {FunctionType} fnType + * @param {[string]} result + */ + const writeSpecialPrimitive = (fnType, result) => { + if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice || + fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) { + const [ob, sb] = + fnType.id === this.typeNameIdOfArray || + fnType.id === this.typeNameIdOfSlice ? + ["[", "]"] : + ["(", ")"]; + pushText({ name: ob, highlighted: fnType.highlighted }, result); + onEachBtwn( + fnType.generics, + nested => writeFn(nested, result), + () => pushText({ name: ", ", highlighted: false }, result), + ); + pushText({ name: sb, highlighted: fnType.highlighted }, result); + return true; + } else if (fnType.id === this.typeNameIdOfReference) { + pushText({ name: "&", highlighted: fnType.highlighted }, result); + let prevHighlighted = false; + onEachBtwn( + fnType.generics, + value => { + prevHighlighted = value.highlighted; + writeFn(value, result); + }, + value => pushText({ + name: " ", + highlighted: prevHighlighted && value.highlighted, + }, result), + ); + return true; + } else if (fnType.id === this.typeNameIdOfFn) { + writeHof(fnType, result); + return true; + } + return false; + }; + /** + * Write a type. This function checks for special types, + * like slices, with their own formatting. It also handles + * updating the where clause and generic type param map. + * + * @param {FunctionType} fnType + * @param {[string]} result + */ + const writeFn = (fnType, result) => { + if (fnType.id < 0) { + const queryId = mgens && mgens.has(fnType.id) ? mgens.get(fnType.id) : null; + if (fnParamNames[-1 - fnType.id] === "") { + for (const nested of fnType.generics) { + writeFn(nested, result); + } + return; + } else if (queryId < 0) { + mappedNames.set( + fnParamNames[-1 - fnType.id], + queryParamNames[-1 - queryId], + ); + } + pushText({ + name: fnParamNames[-1 - fnType.id], + highlighted: !!fnType.highlighted, + }, result); + const where = []; + onEachBtwn( + fnType.generics, + nested => writeFn(nested, where), + () => pushText({ name: " + ", highlighted: false }, where), + ); + if (where.length > 0) { + whereClause.set(fnParamNames[-1 - fnType.id], where); + } + } else { + if (fnType.ty === TY_PRIMITIVE) { + if (writeSpecialPrimitive(fnType, result)) { + return; + } + } else if (fnType.ty === TY_TRAIT && ( + fnType.id === this.typeNameIdOfFn || + fnType.id === this.typeNameIdOfFnMut || + fnType.id === this.typeNameIdOfFnOnce)) { + writeHof(fnType, result); + return; + } + pushText(fnType, result); + let hasBindings = false; + if (fnType.bindings.size > 0) { + onEachBtwn( + fnType.bindings, + ([key, values]) => { + const name = this.assocTypeIdNameMap.get(key); + if (values.length === 1 && values[0].id < 0 && + `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]) { + // the internal `Item=Iterator::Item` type variable should be + // shown in the where clause and name mapping output, but is + // redundant in this spot + for (const value of values) { + writeFn(value, []); + } + return true; + } + if (!hasBindings) { + hasBindings = true; + pushText({ name: "<", highlighted: false }, result); + } + pushText({ name, highlighted: false }, result); + pushText({ + name: values.length > 1 ? "=(" : "=", + highlighted: false, + }, result); + onEachBtwn( + values, + value => writeFn(value, result), + () => pushText({ name: " + ", highlighted: false }, result), + ); + if (values.length > 1) { + pushText({ name: ")", highlighted: false }, result); + } + }, + () => pushText({ name: ", ", highlighted: false }, result), + ); + } + if (fnType.generics.length > 0) { + pushText({ name: hasBindings ? ", " : "<", highlighted: false }, result); + } + onEachBtwn( + fnType.generics, + value => writeFn(value, result), + () => pushText({ name: ", ", highlighted: false }, result), + ); + if (hasBindings || fnType.generics.length > 0) { + pushText({ name: ">", highlighted: false }, result); + } + } + }; + const type = []; + onEachBtwn( + fnInputs, + fnType => writeFn(fnType, type), + () => pushText({ name: ", ", highlighted: false }, type), + ); + pushText({ name: " -> ", highlighted: false }, type); + onEachBtwn( + fnOutput, + fnType => writeFn(fnType, type), + () => pushText({ name: ", ", highlighted: false }, type), + ); + + return {type, mappedNames, whereClause}; + }; + /** * This function takes a result map, and sorts it by various criteria, including edit * distance, substring match, and the crate it comes from. @@ -2097,7 +2489,7 @@ class DocSearch { * @param {string} preferredCrate * @returns {Promise<[ResultObject]>} */ - const sortResults = async(results, isType, preferredCrate) => { + const sortResults = async(results, typeInfo, preferredCrate) => { const userQuery = parsedQuery.userQuery; const normalizedUserQuery = parsedQuery.userQuery.toLowerCase(); const result_list = []; @@ -2207,17 +2599,17 @@ class DocSearch { return 0; }); - return transformResults(result_list); + return transformResults(result_list, typeInfo); }; /** * This function checks if a list of search query `queryElems` can all be found in the * search index (`fnTypes`). * - * This function returns `true` on a match, or `false` if none. If `solutionCb` is + * This function returns highlighted results on a match, or `null`. If `solutionCb` is * supplied, it will call that function with mgens, and that callback can accept or - * reject the result bu returning `true` or `false`. If the callback returns false, - * then this function will try with a different solution, or bail with false if it + * reject the result by returning `true` or `false`. If the callback returns false, + * then this function will try with a different solution, or bail with null if it * runs out of candidates. * * @param {Array} fnTypesIn - The objects to check. @@ -2230,7 +2622,7 @@ class DocSearch { * - Limit checks that Ty matches Vec, * but not Vec>>>> * - * @return {boolean} - Returns true if a match, false otherwise. + * @return {[FunctionType]|null} - Returns highlighed results if a match, null otherwise. */ function unifyFunctionTypes( fnTypesIn, @@ -2241,17 +2633,17 @@ class DocSearch { unboxingDepth, ) { if (unboxingDepth >= UNBOXING_LIMIT) { - return false; + return null; } /** * @type Map|null */ const mgens = mgensIn === null ? null : new Map(mgensIn); if (queryElems.length === 0) { - return !solutionCb || solutionCb(mgens); + return (!solutionCb || solutionCb(mgens)) ? fnTypesIn : null; } if (!fnTypesIn || fnTypesIn.length === 0) { - return false; + return null; } const ql = queryElems.length; const fl = fnTypesIn.length; @@ -2260,7 +2652,7 @@ class DocSearch { if (ql === 1 && queryElems[0].generics.length === 0 && queryElems[0].bindings.size === 0) { const queryElem = queryElems[0]; - for (const fnType of fnTypesIn) { + for (const [i, fnType] of fnTypesIn.entries()) { if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) { continue; } @@ -2272,14 +2664,33 @@ class DocSearch { const mgensScratch = new Map(mgens); mgensScratch.set(fnType.id, queryElem.id); if (!solutionCb || solutionCb(mgensScratch)) { - return true; + const highlighted = [...fnTypesIn]; + highlighted[i] = Object.assign({ + highlighted: true, + }, fnType, { + generics: whereClause[-1 - fnType.id], + }); + return highlighted; } } else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) { // unifyFunctionTypeIsMatchCandidate already checks that ids match - return true; + const highlighted = [...fnTypesIn]; + highlighted[i] = Object.assign({ + highlighted: true, + }, fnType, { + generics: unifyFunctionTypes( + fnType.generics, + queryElem.generics, + whereClause, + mgens ? new Map(mgens) : null, + solutionCb, + unboxingDepth, + ) || fnType.generics, + }); + return highlighted; } } - for (const fnType of fnTypesIn) { + for (const [i, fnType] of fnTypesIn.entries()) { if (!unifyFunctionTypeIsUnboxCandidate( fnType, queryElem, @@ -2296,25 +2707,42 @@ class DocSearch { } const mgensScratch = new Map(mgens); mgensScratch.set(fnType.id, 0); - if (unifyFunctionTypes( + const highlightedGenerics = unifyFunctionTypes( whereClause[(-fnType.id) - 1], queryElems, whereClause, mgensScratch, solutionCb, unboxingDepth + 1, - )) { - return true; + ); + if (highlightedGenerics) { + const highlighted = [...fnTypesIn]; + highlighted[i] = Object.assign({ + highlighted: true, + }, fnType, { + generics: highlightedGenerics, + }); + return highlighted; + } + } else { + const highlightedGenerics = unifyFunctionTypes( + [...Array.from(fnType.bindings.values()).flat(), ...fnType.generics], + queryElems, + whereClause, + mgens ? new Map(mgens) : null, + solutionCb, + unboxingDepth + 1, + ); + if (highlightedGenerics) { + const highlighted = [...fnTypesIn]; + highlighted[i] = Object.assign({}, fnType, { + generics: highlightedGenerics, + bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => { + return [k, highlightedGenerics.splice(0, v.length)]; + })), + }); + return highlighted; } - } else if (unifyFunctionTypes( - [...fnType.generics, ...Array.from(fnType.bindings.values()).flat()], - queryElems, - whereClause, - mgens ? new Map(mgens) : null, - solutionCb, - unboxingDepth + 1, - )) { - return true; } } return false; @@ -2371,6 +2799,8 @@ class DocSearch { if (!queryElemsTmp) { queryElemsTmp = queryElems.slice(0, qlast); } + let unifiedGenerics = []; + let unifiedGenericsMgens = null; const passesUnification = unifyFunctionTypes( fnTypes, queryElemsTmp, @@ -2393,7 +2823,7 @@ class DocSearch { } const simplifiedGenerics = solution.simplifiedGenerics; for (const simplifiedMgens of solution.mgens) { - const passesUnification = unifyFunctionTypes( + unifiedGenerics = unifyFunctionTypes( simplifiedGenerics, queryElem.generics, whereClause, @@ -2401,7 +2831,8 @@ class DocSearch { solutionCb, unboxingDepth, ); - if (passesUnification) { + if (unifiedGenerics) { + unifiedGenericsMgens = simplifiedMgens; return true; } } @@ -2410,7 +2841,23 @@ class DocSearch { unboxingDepth, ); if (passesUnification) { - return true; + passesUnification.length = fl; + passesUnification[flast] = passesUnification[i]; + passesUnification[i] = Object.assign({}, fnType, { + highlighted: true, + generics: unifiedGenerics, + bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => { + return [k, queryElem.bindings.has(k) ? unifyFunctionTypes( + v, + queryElem.bindings.get(k), + whereClause, + unifiedGenericsMgens, + solutionCb, + unboxingDepth, + ) : unifiedGenerics.splice(0, v.length)]; + })), + }); + return passesUnification; } // backtrack fnTypes[flast] = fnTypes[i]; @@ -2445,7 +2892,7 @@ class DocSearch { Array.from(fnType.bindings.values()).flat() : []; const passesUnification = unifyFunctionTypes( - fnTypes.toSpliced(i, 1, ...generics, ...bindings), + fnTypes.toSpliced(i, 1, ...bindings, ...generics), queryElems, whereClause, mgensScratch, @@ -2453,10 +2900,24 @@ class DocSearch { unboxingDepth + 1, ); if (passesUnification) { - return true; + const highlightedGenerics = passesUnification.slice( + i, + i + generics.length + bindings.length, + ); + const highlightedFnType = Object.assign({}, fnType, { + generics: highlightedGenerics, + bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => { + return [k, highlightedGenerics.splice(0, v.length)]; + })), + }); + return passesUnification.toSpliced( + i, + generics.length + bindings.length, + highlightedFnType, + ); } } - return false; + return null; } /** * Check if this function is a match candidate. @@ -2627,7 +3088,7 @@ class DocSearch { } }); if (simplifiedGenerics.length > 0) { - simplifiedGenerics = [...simplifiedGenerics, ...binds]; + simplifiedGenerics = [...binds, ...simplifiedGenerics]; } else { simplifiedGenerics = binds; } @@ -3285,10 +3746,11 @@ class DocSearch { innerRunQuery(); } + const isType = parsedQuery.foundElems !== 1 || parsedQuery.hasReturnArrow; const [sorted_in_args, sorted_returned, sorted_others] = await Promise.all([ - sortResults(results_in_args, true, currentCrate), - sortResults(results_returned, true, currentCrate), - sortResults(results_others, false, currentCrate), + sortResults(results_in_args, "elems", currentCrate), + sortResults(results_returned, "returned", currentCrate), + sortResults(results_others, (isType ? "query" : null), currentCrate), ]); const ret = createQueryResults( sorted_in_args, @@ -3315,6 +3777,7 @@ class DocSearch { } } + // ==================== Core search logic end ==================== let rawSearchIndex; @@ -3446,15 +3909,18 @@ function focusSearchResult() { * @param {Array} array - The search results for this tab * @param {ParsedQuery} query * @param {boolean} display - True if this is the active tab + * @param {"sig"|"elems"|"returned"|null} typeInfo */ async function addTab(array, query, display) { const extraClass = display ? " active" : ""; - const output = document.createElement("div"); + const output = document.createElement( + array.length === 0 && query.error === null ? "div" : "ul", + ); if (array.length > 0) { output.className = "search-results " + extraClass; - for (const item of array) { + const lis = Promise.all(array.map(async item => { const name = item.name; const type = itemTypes[item.ty]; const longType = longItemTypes[item.ty]; @@ -3464,7 +3930,7 @@ async function addTab(array, query, display) { link.className = "result-" + type; link.href = item.href; - const resultName = document.createElement("div"); + const resultName = document.createElement("span"); resultName.className = "result-name"; resultName.insertAdjacentHTML( @@ -3487,10 +3953,73 @@ ${item.displayPath}${name}\ const description = document.createElement("div"); description.className = "desc"; description.insertAdjacentHTML("beforeend", item.desc); + if (item.displayTypeSignature) { + const {type, mappedNames, whereClause} = await item.displayTypeSignature; + const displayType = document.createElement("div"); + type.forEach((value, index) => { + if (index % 2 !== 0) { + const highlight = document.createElement("strong"); + highlight.appendChild(document.createTextNode(value)); + displayType.appendChild(highlight); + } else { + displayType.appendChild(document.createTextNode(value)); + } + }); + if (mappedNames.size > 0 || whereClause.size > 0) { + let addWhereLineFn = () => { + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode("where")); + displayType.appendChild(line); + addWhereLineFn = () => {}; + }; + for (const [name, qname] of mappedNames) { + // don't care unless the generic name is different + if (name === qname) { + continue; + } + addWhereLineFn(); + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode(` ${qname} matches `)); + const lineStrong = document.createElement("strong"); + lineStrong.appendChild(document.createTextNode(name)); + line.appendChild(lineStrong); + displayType.appendChild(line); + } + for (const [name, innerType] of whereClause) { + // don't care unless there's at least one highlighted entry + if (innerType.length <= 1) { + continue; + } + addWhereLineFn(); + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode(` ${name}: `)); + innerType.forEach((value, index) => { + if (index % 2 !== 0) { + const highlight = document.createElement("strong"); + highlight.appendChild(document.createTextNode(value)); + line.appendChild(highlight); + } else { + line.appendChild(document.createTextNode(value)); + } + }); + displayType.appendChild(line); + } + } + displayType.className = "type-signature"; + link.appendChild(displayType); + } link.appendChild(description); - output.appendChild(link); - } + return link; + })); + lis.then(lis => { + for (const li of lis) { + output.appendChild(li); + } + }); } else if (query.error === null) { output.className = "search-failed" + extraClass; output.innerHTML = "No results :(
" + @@ -3507,7 +4036,7 @@ ${item.displayPath}${name}\ "href=\"https://docs.rs\">Docs.rs
for documentation of crates released on" + " crates.io."; } - return [output, array.length]; + return output; } function makeTabHeader(tabNb, text, nbElems) { @@ -3564,24 +4093,18 @@ async function showResults(results, go_to_first, filterCrates) { currentResults = results.query.userQuery; - const [ret_others, ret_in_args, ret_returned] = await Promise.all([ - addTab(results.others, results.query, true), - addTab(results.in_args, results.query, false), - addTab(results.returned, results.query, false), - ]); - // Navigate to the relevant tab if the current tab is empty, like in case users search // for "-> String". If they had selected another tab previously, they have to click on // it again. let currentTab = searchState.currentTab; - if ((currentTab === 0 && ret_others[1] === 0) || - (currentTab === 1 && ret_in_args[1] === 0) || - (currentTab === 2 && ret_returned[1] === 0)) { - if (ret_others[1] !== 0) { + if ((currentTab === 0 && results.others.length === 0) || + (currentTab === 1 && results.in_args.length === 0) || + (currentTab === 2 && results.returned.length === 0)) { + if (results.others.length !== 0) { currentTab = 0; - } else if (ret_in_args[1] !== 0) { + } else if (results.in_args.length) { currentTab = 1; - } else if (ret_returned[1] !== 0) { + } else if (results.returned.length) { currentTab = 2; } } @@ -3610,14 +4133,14 @@ async function showResults(results, go_to_first, filterCrates) { }); output += `

Query parser error: "${error.join("")}".

`; output += "
" + - makeTabHeader(0, "In Names", ret_others[1]) + + makeTabHeader(0, "In Names", results.others.length) + "
"; currentTab = 0; } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) { output += "
" + - makeTabHeader(0, "In Names", ret_others[1]) + - makeTabHeader(1, "In Parameters", ret_in_args[1]) + - makeTabHeader(2, "In Return Types", ret_returned[1]) + + makeTabHeader(0, "In Names", results.others.length) + + makeTabHeader(1, "In Parameters", results.in_args.length) + + makeTabHeader(2, "In Return Types", results.returned.length) + "
"; } else { const signatureTabTitle = @@ -3625,7 +4148,7 @@ async function showResults(results, go_to_first, filterCrates) { results.query.returned.length === 0 ? "In Function Parameters" : "In Function Signatures"; output += "
" + - makeTabHeader(0, signatureTabTitle, ret_others[1]) + + makeTabHeader(0, signatureTabTitle, results.others.length) + "
"; currentTab = 0; } @@ -3647,11 +4170,17 @@ async function showResults(results, go_to_first, filterCrates) { `Consider searching for "${targ}" instead.`; } + const [ret_others, ret_in_args, ret_returned] = await Promise.all([ + addTab(results.others, results.query, currentTab === 0), + addTab(results.in_args, results.query, currentTab === 1), + addTab(results.returned, results.query, currentTab === 2), + ]); + const resultsElem = document.createElement("div"); resultsElem.id = "results"; - resultsElem.appendChild(ret_others[0]); - resultsElem.appendChild(ret_in_args[0]); - resultsElem.appendChild(ret_returned[0]); + resultsElem.appendChild(ret_others); + resultsElem.appendChild(ret_in_args); + resultsElem.appendChild(ret_returned); search.innerHTML = output; if (searchState.rustdocToolbar) { @@ -3933,4 +4462,3 @@ if (typeof window !== "undefined") { // exports. initSearch(new Map()); } -})(); diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index 63cda4111e6a..7aa5e102e6d2 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -2,6 +2,14 @@ const fs = require("fs"); const path = require("path"); + +function arrayToCode(array) { + return array.map((value, index) => { + value = value.split(" ").join(" "); + return (index % 2 === 1) ? ("`" + value + "`") : value; + }).join(""); +} + function loadContent(content) { const Module = module.constructor; const m = new Module(); @@ -180,15 +188,7 @@ function valueCheck(fullPath, expected, result, error_text, queryName) { if (!result_v.forEach) { throw result_v; } - result_v.forEach((value, index) => { - value = value.split(" ").join(" "); - if (index % 2 === 1) { - result_v[index] = "`" + value + "`"; - } else { - result_v[index] = value; - } - }); - result_v = result_v.join(""); + result_v = arrayToCode(result_v); } const obj_path = fullPath + (fullPath.length > 0 ? "." : "") + key; valueCheck(obj_path, expected[key], result_v, error_text, queryName); @@ -436,9 +436,41 @@ function loadSearchJS(doc_folder, resource_suffix) { searchModule.initSearch(searchIndex.searchIndex); const docSearch = searchModule.docSearch; return { - doSearch: function(queryStr, filterCrate, currentCrate) { - return docSearch.execQuery(searchModule.parseQuery(queryStr), + doSearch: async function(queryStr, filterCrate, currentCrate) { + const result = await docSearch.execQuery(searchModule.parseQuery(queryStr), filterCrate, currentCrate); + for (const tab in result) { + if (!Object.prototype.hasOwnProperty.call(result, tab)) { + continue; + } + if (!(result[tab] instanceof Array)) { + continue; + } + for (const entry of result[tab]) { + for (const key in entry) { + if (!Object.prototype.hasOwnProperty.call(entry, key)) { + continue; + } + if (key === "displayTypeSignature") { + const {type, mappedNames, whereClause} = + await entry.displayTypeSignature; + entry.displayType = arrayToCode(type); + entry.displayMappedNames = [...mappedNames.entries()] + .map(([name, qname]) => { + return `${name} = ${qname}`; + }).join(", "); + entry.displayWhereClause = [...whereClause.entries()] + .flatMap(([name, value]) => { + if (value.length === 0) { + return []; + } + return [`${name}: ${arrayToCode(value)}`]; + }).join(", "); + } + } + } + } + return result; }, getCorrections: function(queryStr, filterCrate, currentCrate) { const parsedQuery = searchModule.parseQuery(queryStr); diff --git a/tests/rustdoc-gui/search-about-this-result.goml b/tests/rustdoc-gui/search-about-this-result.goml new file mode 100644 index 000000000000..62780d01ed7e --- /dev/null +++ b/tests/rustdoc-gui/search-about-this-result.goml @@ -0,0 +1,42 @@ +// Check the "About this Result" popover. +// Try a complex result. +go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=scroll_traits::Iterator,(T->bool)->(Extend,Extend)" + +// These two commands are used to be sure the search will be run. +focus: ".search-input" +press-key: "Enter" + +wait-for: "#search-tabs" +assert-count: ("#search-tabs button", 1) +assert-count: (".search-results > a", 1) + +assert: "//div[@class='type-signature']/strong[text()='Iterator']" +assert: "//div[@class='type-signature']/strong[text()='(']" +assert: "//div[@class='type-signature']/strong[text()=')']" + +assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='FnMut']" +assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Iterator::Item']" +assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='bool']" +assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Extend']" + +assert-text: ("div.type-signature div.where:nth-child(4)", "where") +assert-text: ("div.type-signature div.where:nth-child(5)", " T matches Iterator::Item") +assert-text: ("div.type-signature div.where:nth-child(6)", " F: FnMut (&Iterator::Item) -> bool") +assert-text: ("div.type-signature div.where:nth-child(7)", " B: Default + Extend") + +// Try a simple result that *won't* give an info box. +go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=F->lib2::WhereWhitespace" + +// These two commands are used to be sure the search will be run. +focus: ".search-input" +press-key: "Enter" + +wait-for: "#search-tabs" +assert-text: ("//div[@class='type-signature']", "F -> WhereWhitespace") +assert-count: ("#search-tabs button", 1) +assert-count: (".search-results > a", 1) +assert-count: ("//div[@class='type-signature']/div[@class='where']", 0) + +assert: "//div[@class='type-signature']/strong[text()='F']" +assert: "//div[@class='type-signature']/strong[text()='WhereWhitespace']" +assert: "//div[@class='type-signature']/strong[text()='T']" diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js index e154fa707ab3..1690d5dc8b5b 100644 --- a/tests/rustdoc-js-std/option-type-signatures.js +++ b/tests/rustdoc-js-std/option-type-signatures.js @@ -6,79 +6,217 @@ const EXPECTED = [ { 'query': 'option, fnonce -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'map' }, + { + 'path': 'std::option::Option', + 'name': 'map', + 'displayType': '`Option`, F -> `Option`', + 'displayWhereClause': "F: `FnOnce` (T) -> U", + }, + ], + }, + { + 'query': 'option, fnonce -> option', + 'others': [ + { + 'path': 'std::option::Option', + 'name': 'map', + 'displayType': '`Option`<`T`>, F -> `Option`', + 'displayWhereClause': "F: `FnOnce` (T) -> U", + }, ], }, { 'query': 'option -> default', 'others': [ - { 'path': 'std::option::Option', 'name': 'unwrap_or_default' }, - { 'path': 'std::option::Option', 'name': 'get_or_insert_default' }, + { + 'path': 'std::option::Option', + 'name': 'unwrap_or_default', + 'displayType': '`Option` -> `T`', + 'displayWhereClause': "T: `Default`", + }, + { + 'path': 'std::option::Option', + 'name': 'get_or_insert_default', + 'displayType': '&mut `Option` -> &mut `T`', + 'displayWhereClause': "T: `Default`", + }, ], }, { 'query': 'option -> []', 'others': [ - { 'path': 'std::option::Option', 'name': 'as_slice' }, - { 'path': 'std::option::Option', 'name': 'as_mut_slice' }, + { + 'path': 'std::option::Option', + 'name': 'as_slice', + 'displayType': '&`Option` -> &`[`T`]`', + }, + { + 'path': 'std::option::Option', + 'name': 'as_mut_slice', + 'displayType': '&mut `Option` -> &mut `[`T`]`', + }, ], }, { 'query': 'option, option -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'or' }, - { 'path': 'std::option::Option', 'name': 'xor' }, + { + 'path': 'std::option::Option', + 'name': 'or', + 'displayType': '`Option`<`T`>, `Option`<`T`> -> `Option`<`T`>', + }, + { + 'path': 'std::option::Option', + 'name': 'xor', + 'displayType': '`Option`<`T`>, `Option`<`T`> -> `Option`<`T`>', + }, ], }, { 'query': 'option, option -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'and' }, - { 'path': 'std::option::Option', 'name': 'zip' }, + { + 'path': 'std::option::Option', + 'name': 'and', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>', + }, + { + 'path': 'std::option::Option', + 'name': 'zip', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(T, `U`)>', + }, ], }, { 'query': 'option, option -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'and' }, - { 'path': 'std::option::Option', 'name': 'zip' }, + { + 'path': 'std::option::Option', + 'name': 'and', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>', + }, + { + 'path': 'std::option::Option', + 'name': 'zip', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, U)>', + }, ], }, { 'query': 'option, option -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'zip' }, + { + 'path': 'std::option::Option', + 'name': 'zip', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, `U`)>', + }, ], }, { 'query': 'option, e -> result', 'others': [ - { 'path': 'std::option::Option', 'name': 'ok_or' }, - { 'path': 'std::result::Result', 'name': 'transpose' }, + { + 'path': 'std::option::Option', + 'name': 'ok_or', + 'displayType': '`Option`<`T`>, `E` -> `Result`<`T`, `E`>', + }, + { + 'path': 'std::result::Result', + 'name': 'transpose', + 'displayType': 'Result<`Option`<`T`>, `E`> -> Option<`Result`<`T`, `E`>>', + }, ], }, { 'query': 'result, e> -> option>', 'others': [ - { 'path': 'std::result::Result', 'name': 'transpose' }, + { + 'path': 'std::result::Result', + 'name': 'transpose', + 'displayType': '`Result`<`Option`<`T`>, `E`> -> `Option`<`Result`<`T`, `E`>>', + }, ], }, { 'query': 'option, option -> bool', 'others': [ - { 'path': 'std::option::Option', 'name': 'eq' }, + { + 'path': 'std::option::Option', + 'name': 'eq', + 'displayType': '&`Option`<`T`>, &`Option`<`T`> -> `bool`', + }, ], }, { 'query': 'option> -> option', 'others': [ - { 'path': 'std::option::Option', 'name': 'flatten' }, + { + 'path': 'std::option::Option', + 'name': 'flatten', + 'displayType': '`Option`<`Option`<`T`>> -> `Option`<`T`>', + }, ], }, { 'query': 'option', 'returned': [ - { 'path': 'std::result::Result', 'name': 'ok' }, + { + 'path': 'std::result::Result', + 'name': 'ok', + 'displayType': 'Result -> `Option`<`T`>', + }, + ], + }, + { + 'query': 'option, (fnonce () -> u) -> option', + 'others': [ + { + 'path': 'std::option::Option', + 'name': 'map', + 'displayType': '`Option`<`T`>, F -> `Option`', + 'displayMappedNames': `T = t, U = u`, + 'displayWhereClause': "F: `FnOnce` (T) -> `U`", + }, + { + 'path': 'std::option::Option', + 'name': 'and_then', + 'displayType': '`Option`<`T`>, F -> `Option`', + 'displayMappedNames': `T = t, U = u`, + 'displayWhereClause': "F: `FnOnce` (T) -> Option<`U`>", + }, + { + 'path': 'std::option::Option', + 'name': 'zip_with', + 'displayType': 'Option, `Option`<`U`>, F -> `Option`', + 'displayMappedNames': `U = t, R = u`, + 'displayWhereClause': "F: `FnOnce` (T, U) -> `R`", + }, + { + 'path': 'std::task::Poll', + 'name': 'map_ok', + 'displayType': 'Poll<`Option`>>, F -> Poll<`Option`>>', + 'displayMappedNames': `T = t, U = u`, + 'displayWhereClause': "F: `FnOnce` (T) -> `U`", + }, + { + 'path': 'std::task::Poll', + 'name': 'map_err', + 'displayType': 'Poll<`Option`>>, F -> Poll<`Option`>>', + 'displayMappedNames': `T = t, U = u`, + 'displayWhereClause': "F: `FnOnce` (E) -> `U`", + }, + ], + }, + { + 'query': 'option, (fnonce () -> option) -> option', + 'others': [ + { + 'path': 'std::option::Option', + 'name': 'and_then', + 'displayType': '`Option`<`T`>, F -> `Option`', + 'displayMappedNames': `T = t, U = u`, + 'displayWhereClause': "F: `FnOnce` (T) -> `Option`<`U`>", + }, ], }, ]; diff --git a/tests/rustdoc-js/assoc-type-unbound.js b/tests/rustdoc-js/assoc-type-unbound.js new file mode 100644 index 000000000000..611b8bd1501c --- /dev/null +++ b/tests/rustdoc-js/assoc-type-unbound.js @@ -0,0 +1,39 @@ +// exact-check + +const EXPECTED = [ + // Trait-associated types (that is, associated types with no constraints) + // are treated like type parameters, so that you can "pattern match" + // them. We should avoid redundant output (no `Item=MyIter::Item` stuff) + // and should give reasonable results + { + 'query': 'MyIter -> Option', + 'correction': null, + 'others': [ + { + 'path': 'assoc_type_unbound::MyIter', + 'name': 'next', + 'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>', + 'displayMappedNames': 'MyIter::Item = T', + 'displayWhereClause': '', + }, + ], + }, + { + 'query': 'MyIter -> Option', + 'correction': null, + 'others': [ + { + 'path': 'assoc_type_unbound::MyIter', + 'name': 'next', + 'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>', + 'displayMappedNames': 'MyIter::Item = T', + 'displayWhereClause': '', + }, + ], + }, + { + 'query': 'MyIter -> Option', + 'correction': null, + 'others': [], + }, +]; diff --git a/tests/rustdoc-js/assoc-type-unbound.rs b/tests/rustdoc-js/assoc-type-unbound.rs new file mode 100644 index 000000000000..713b77b50072 --- /dev/null +++ b/tests/rustdoc-js/assoc-type-unbound.rs @@ -0,0 +1,4 @@ +pub trait MyIter { + type Item; + fn next(&mut self) -> Option; +} diff --git a/tests/rustdoc-js/assoc-type.js b/tests/rustdoc-js/assoc-type.js index eec4e7a8258f..0edf10e794ed 100644 --- a/tests/rustdoc-js/assoc-type.js +++ b/tests/rustdoc-js/assoc-type.js @@ -7,16 +7,40 @@ const EXPECTED = [ 'query': 'iterator -> u32', 'correction': null, 'others': [ - { 'path': 'assoc_type::my', 'name': 'other_fn' }, - { 'path': 'assoc_type', 'name': 'my_fn' }, + { + 'path': 'assoc_type::my', + 'name': 'other_fn', + 'displayType': 'X -> `u32`', + 'displayMappedNames': '', + 'displayWhereClause': 'X: `Iterator`<`Something`>', + }, + { + 'path': 'assoc_type', + 'name': 'my_fn', + 'displayType': 'X -> `u32`', + 'displayMappedNames': '', + 'displayWhereClause': 'X: `Iterator`', + }, ], }, { 'query': 'iterator', 'correction': null, 'in_args': [ - { 'path': 'assoc_type::my', 'name': 'other_fn' }, - { 'path': 'assoc_type', 'name': 'my_fn' }, + { + 'path': 'assoc_type::my', + 'name': 'other_fn', + 'displayType': 'X -> u32', + 'displayMappedNames': '', + 'displayWhereClause': 'X: `Iterator`<`Something`>', + }, + { + 'path': 'assoc_type', + 'name': 'my_fn', + 'displayType': 'X -> u32', + 'displayMappedNames': '', + 'displayWhereClause': 'X: `Iterator`', + }, ], }, { @@ -26,8 +50,20 @@ const EXPECTED = [ { 'path': 'assoc_type', 'name': 'Something' }, ], 'in_args': [ - { 'path': 'assoc_type::my', 'name': 'other_fn' }, - { 'path': 'assoc_type', 'name': 'my_fn' }, + { + 'path': 'assoc_type::my', + 'name': 'other_fn', + 'displayType': '`X` -> u32', + 'displayMappedNames': '', + 'displayWhereClause': 'X: Iterator<`Something`>', + }, + { + 'path': 'assoc_type', + 'name': 'my_fn', + 'displayType': '`X` -> u32', + 'displayMappedNames': '', + 'displayWhereClause': 'X: Iterator', + }, ], }, // if I write an explicit binding, only it shows up diff --git a/tests/rustdoc-js/generics-trait.js b/tests/rustdoc-js/generics-trait.js index a71393b5e050..8da9c67050e6 100644 --- a/tests/rustdoc-js/generics-trait.js +++ b/tests/rustdoc-js/generics-trait.js @@ -5,10 +5,22 @@ const EXPECTED = [ 'query': 'Result', 'correction': null, 'in_args': [ - { 'path': 'generics_trait', 'name': 'beta' }, + { + 'path': 'generics_trait', + 'name': 'beta', + 'displayType': '`Result`<`T`, ()> -> ()', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `SomeTrait`', + }, ], 'returned': [ - { 'path': 'generics_trait', 'name': 'bet' }, + { + 'path': 'generics_trait', + 'name': 'bet', + 'displayType': ' -> `Result`<`T`, ()>', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `SomeTrait`', + }, ], }, { @@ -25,20 +37,44 @@ const EXPECTED = [ 'query': 'OtherThingxxxxxxxx', 'correction': null, 'in_args': [ - { 'path': 'generics_trait', 'name': 'alpha' }, + { + 'path': 'generics_trait', + 'name': 'alpha', + 'displayType': 'Result<`T`, ()> -> ()', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `OtherThingxxxxxxxx`', + }, ], 'returned': [ - { 'path': 'generics_trait', 'name': 'alef' }, + { + 'path': 'generics_trait', + 'name': 'alef', + 'displayType': ' -> Result<`T`, ()>', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `OtherThingxxxxxxxx`', + }, ], }, { 'query': 'OtherThingxxxxxxxy', 'correction': 'OtherThingxxxxxxxx', 'in_args': [ - { 'path': 'generics_trait', 'name': 'alpha' }, + { + 'path': 'generics_trait', + 'name': 'alpha', + 'displayType': 'Result<`T`, ()> -> ()', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `OtherThingxxxxxxxx`', + }, ], 'returned': [ - { 'path': 'generics_trait', 'name': 'alef' }, + { + 'path': 'generics_trait', + 'name': 'alef', + 'displayType': ' -> Result<`T`, ()>', + 'displayMappedNames': '', + 'displayWhereClause': 'T: `OtherThingxxxxxxxx`', + }, ], }, ]; From 12dc24f46007f82b93ed85614347a42d47580afa Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Sep 2024 18:18:01 -0700 Subject: [PATCH 017/214] rustdoc-search: simplify rules for generics and type params This commit is a response to feedback on the displayed type signatures results, by making generics act stricter. Generics are tightened by making order significant. This means `Vec` now matches only with a true vector of allocators, instead of matching the second type param. It also makes unboxing within generics stricter, so `Result` only matches if `B` is in the error type and `A` is in the success type. The top level of the function search is unaffected. Find the discussion on: * * * --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_passes/messages.ftl | 3 + compiler/rustc_passes/src/check_attr.rs | 27 +- compiler/rustc_passes/src/errors.rs | 7 + compiler/rustc_span/src/symbol.rs | 1 + library/alloc/src/boxed.rs | 1 + library/alloc/src/rc.rs | 1 + library/alloc/src/sync.rs | 1 + library/core/src/future/future.rs | 1 + library/core/src/option.rs | 1 + library/core/src/result.rs | 1 + .../rustdoc/src/read-documentation/search.md | 12 +- src/librustdoc/html/render/search_index.rs | 74 +++- src/librustdoc/html/static/js/search.js | 352 ++++++++++++++---- .../rustdoc-gui/search-about-this-result.goml | 4 +- tests/rustdoc-js-std/bufread-fill-buf.js | 11 +- .../rustdoc-js-std/option-type-signatures.js | 31 +- tests/rustdoc-js-std/vec-type-signatures.js | 12 + tests/rustdoc-js/assoc-type-backtrack.js | 48 ++- tests/rustdoc-js/assoc-type-backtrack.rs | 4 + tests/rustdoc-js/assoc-type-unbound.js | 4 +- tests/rustdoc-js/assoc-type.rs | 12 +- tests/rustdoc-js/generics-impl.js | 8 +- tests/rustdoc-js/generics-impl.rs | 4 +- .../generics-match-ambiguity-no-unbox.js | 68 ++++ .../generics-match-ambiguity-no-unbox.rs | 18 + tests/rustdoc-js/generics-match-ambiguity.js | 12 +- tests/rustdoc-js/generics-match-ambiguity.rs | 6 +- tests/rustdoc-js/generics-nested.js | 5 +- tests/rustdoc-js/generics-unbox.js | 4 - tests/rustdoc-js/generics-unbox.rs | 8 + tests/rustdoc-js/generics.js | 16 +- tests/rustdoc-js/hof.js | 25 +- tests/rustdoc-js/looks-like-rustc-interner.js | 18 +- tests/rustdoc-js/nested-unboxed.js | 9 +- tests/rustdoc-js/nested-unboxed.rs | 3 + tests/rustdoc-js/reference.js | 15 +- tests/rustdoc-js/tuple-unit.js | 4 +- .../feature-gate-rustdoc_internals.rs | 3 + .../feature-gate-rustdoc_internals.stderr | 12 +- 40 files changed, 630 insertions(+), 217 deletions(-) create mode 100644 tests/rustdoc-js/generics-match-ambiguity-no-unbox.js create mode 100644 tests/rustdoc-js/generics-match-ambiguity-no-unbox.rs diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index d646150a620c..396aac345156 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -204,6 +204,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { "meant for internal use only" { keyword => rustdoc_internals fake_variadic => rustdoc_internals + search_unbox => rustdoc_internals } ); } diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index e5e70ba20338..3536b3fc4cd6 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -234,6 +234,9 @@ passes_doc_masked_only_extern_crate = passes_doc_rust_logo = the `#[doc(rust_logo)]` attribute is used for Rust branding +passes_doc_search_unbox_invalid = + `#[doc(search_unbox)]` should be used on generic structs and enums + passes_doc_test_literal = `#![doc(test(...)]` does not take a literal passes_doc_test_takes_list = diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 0a2926c04044..4cf523b05568 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -16,8 +16,8 @@ use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, B use rustc_hir::def_id::LocalModDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{ - self as hir, self, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig, ForeignItem, HirId, Item, ItemKind, - MethodKind, Safety, Target, TraitItem, + self as hir, self, AssocItemKind, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig, ForeignItem, HirId, + Item, ItemKind, MethodKind, Safety, Target, TraitItem, }; use rustc_macros::LintDiagnostic; use rustc_middle::hir::nested_filter; @@ -940,6 +940,23 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) { + let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else { + self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() }); + return; + }; + match item.kind { + ItemKind::Enum(_, generics) | ItemKind::Struct(_, generics) + if generics.params.len() != 0 => {} + ItemKind::Trait(_, _, generics, _, items) + if generics.params.len() != 0 + || items.iter().any(|item| matches!(item.kind, AssocItemKind::Type)) => {} + _ => { + self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() }); + } + } + } + /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. /// /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or @@ -1152,6 +1169,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + sym::search_unbox => { + if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") { + self.check_doc_search_unbox(meta, hir_id); + } + } + sym::test => { if self.check_attr_crate_level(attr, meta, hir_id) { self.check_test_attr(meta, hir_id); diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 8bd767c1243d..494be13b86a4 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -244,6 +244,13 @@ pub(crate) struct DocKeywordOnlyImpl { pub span: Span, } +#[derive(Diagnostic)] +#[diag(passes_doc_search_unbox_invalid)] +pub(crate) struct DocSearchUnboxInvalid { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(passes_doc_inline_conflict)] #[help] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 890c4fdafef8..b2e8adb06043 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1762,6 +1762,7 @@ symbols! { saturating_add, saturating_div, saturating_sub, + search_unbox, select_unpredictable, self_in_typedefs, self_struct_ctor, diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index e4956c7c53c8..c5024a05ed63 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -225,6 +225,7 @@ pub use thin::ThinBox; #[fundamental] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] +#[cfg_attr(not(bootstrap), doc(search_unbox))] // The declaration of the `Box` struct must be kept in sync with the // compiler or ICEs will happen. pub struct Box< diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index fc8646e96d94..06addb4edb2b 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -307,6 +307,7 @@ fn rc_inner_layout_for_value_layout(layout: Layout) -> Layout { /// `value.get_mut()`. This avoids conflicts with methods of the inner type `T`. /// /// [get_mut]: Rc::get_mut +#[cfg_attr(not(bootstrap), doc(search_unbox))] #[cfg_attr(not(test), rustc_diagnostic_item = "Rc")] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 98a2fe242570..e6a2cf009ce7 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -235,6 +235,7 @@ macro_rules! acquire { /// counting in general. /// /// [rc_examples]: crate::rc#examples +#[cfg_attr(not(bootstrap), doc(search_unbox))] #[cfg_attr(not(test), rustc_diagnostic_item = "Arc")] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs index ca1c2d1ca1f2..234914c20fc3 100644 --- a/library/core/src/future/future.rs +++ b/library/core/src/future/future.rs @@ -25,6 +25,7 @@ use crate::task::{Context, Poll}; /// [`async`]: ../../std/keyword.async.html /// [`Waker`]: crate::task::Waker #[doc(notable_trait)] +#[cfg_attr(not(bootstrap), doc(search_unbox))] #[must_use = "futures do nothing unless you `.await` or poll them"] #[stable(feature = "futures_api", since = "1.36.0")] #[lang = "future_trait"] diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 2aa4f1723680..461879386225 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -563,6 +563,7 @@ use crate::pin::Pin; use crate::{cmp, convert, hint, mem, slice}; /// The `Option` type. See [the module level documentation](self) for more. +#[cfg_attr(not(bootstrap), doc(search_unbox))] #[derive(Copy, Eq, Debug, Hash)] #[rustc_diagnostic_item = "Option"] #[lang = "Option"] diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 330d1eb14edb..b450123c5aa9 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -520,6 +520,7 @@ use crate::{convert, fmt, hint}; /// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]). /// /// See the [module documentation](self) for details. +#[cfg_attr(not(bootstrap), doc(search_unbox))] #[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] #[must_use = "this `Result` may be an `Err` variant, which should be handled"] #[rustc_diagnostic_item = "Result"] diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md index e912ca0fe5b4..718d2201c3a5 100644 --- a/src/doc/rustdoc/src/read-documentation/search.md +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -130,29 +130,31 @@ pub trait MyTrait { /// This function can be found using the following search queries: /// /// MyTrait -> bool -/// MyTrait -> bool /// MyTrait -> bool -/// MyTrait -> bool /// /// The following queries, however, will *not* match it: /// /// MyTrait -> bool /// MyTrait -> bool +/// MyTrait -> bool +/// MyTrait -> bool pub fn my_fn(x: impl MyTrait) -> bool { true } ``` -Generics and function parameters are order-agnostic, but sensitive to nesting +Function parameters are order-agnostic, but sensitive to nesting and number of matches. For example, a function with the signature `fn read_all(&mut self: impl Read) -> Result, Error>` will match these queries: * `&mut Read -> Result, Error>` * `Read -> Result, Error>` -* `Read -> Result` * `Read -> Result>` * `Read -> u8` -But it *does not* match `Result` or `Result>`. +But it *does not* match `Result` or `Result>`, +because those are nested incorrectly, and it does not match +`Result>` or `Result`, because those are +in the wrong order. To search for a function that accepts a function as a parameter, like `Iterator::all`, wrap the nested signature in parenthesis, diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index ee2fc1f44b34..f91fdfa1fb5f 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -13,8 +13,8 @@ use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer}; use thin_vec::ThinVec; use tracing::instrument; -use crate::clean; use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; +use crate::clean::{self, utils}; use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; @@ -66,7 +66,7 @@ pub(crate) fn build_index<'tcx>( let mut associated_types = FxHashMap::default(); // item type, display path, re-exported internal path - let mut crate_paths: Vec<(ItemType, Vec, Option>)> = vec![]; + let mut crate_paths: Vec<(ItemType, Vec, Option>, bool)> = vec![]; // Attach all orphan items to the type's definition if the type // has since been learned. @@ -132,10 +132,11 @@ pub(crate) fn build_index<'tcx>( map: &mut FxHashMap, itemid: F, lastpathid: &mut isize, - crate_paths: &mut Vec<(ItemType, Vec, Option>)>, + crate_paths: &mut Vec<(ItemType, Vec, Option>, bool)>, item_type: ItemType, path: &[Symbol], exact_path: Option<&[Symbol]>, + search_unbox: bool, ) -> RenderTypeId { match map.entry(itemid) { Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()), @@ -147,6 +148,7 @@ pub(crate) fn build_index<'tcx>( item_type, path.to_vec(), exact_path.map(|path| path.to_vec()), + search_unbox, )); RenderTypeId::Index(pathid) } @@ -160,9 +162,21 @@ pub(crate) fn build_index<'tcx>( primitives: &mut FxHashMap, associated_types: &mut FxHashMap, lastpathid: &mut isize, - crate_paths: &mut Vec<(ItemType, Vec, Option>)>, + crate_paths: &mut Vec<(ItemType, Vec, Option>, bool)>, + tcx: TyCtxt<'_>, ) -> Option { + use crate::clean::PrimitiveType; let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache; + let search_unbox = match id { + RenderTypeId::Mut => false, + RenderTypeId::DefId(defid) => utils::has_doc_flag(tcx, defid, sym::search_unbox), + RenderTypeId::Primitive(PrimitiveType::Reference | PrimitiveType::Tuple) => true, + RenderTypeId::Primitive(..) => false, + RenderTypeId::AssociatedType(..) => false, + // this bool is only used by `insert_into_map`, so it doesn't matter what we set here + // because Index means we've already inserted into the map + RenderTypeId::Index(_) => false, + }; match id { RenderTypeId::Mut => Some(insert_into_map( primitives, @@ -172,6 +186,7 @@ pub(crate) fn build_index<'tcx>( ItemType::Keyword, &[kw::Mut], None, + search_unbox, )), RenderTypeId::DefId(defid) => { if let Some(&(ref fqp, item_type)) = @@ -195,6 +210,7 @@ pub(crate) fn build_index<'tcx>( item_type, fqp, exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp), + search_unbox, )) } else { None @@ -210,6 +226,7 @@ pub(crate) fn build_index<'tcx>( ItemType::Primitive, &[sym], None, + search_unbox, )) } RenderTypeId::Index(_) => Some(id), @@ -221,6 +238,7 @@ pub(crate) fn build_index<'tcx>( ItemType::AssocType, &[sym], None, + search_unbox, )), } } @@ -232,7 +250,8 @@ pub(crate) fn build_index<'tcx>( primitives: &mut FxHashMap, associated_types: &mut FxHashMap, lastpathid: &mut isize, - crate_paths: &mut Vec<(ItemType, Vec, Option>)>, + crate_paths: &mut Vec<(ItemType, Vec, Option>, bool)>, + tcx: TyCtxt<'_>, ) { if let Some(generics) = &mut ty.generics { for item in generics { @@ -244,6 +263,7 @@ pub(crate) fn build_index<'tcx>( associated_types, lastpathid, crate_paths, + tcx, ); } } @@ -257,6 +277,7 @@ pub(crate) fn build_index<'tcx>( associated_types, lastpathid, crate_paths, + tcx, ); let Some(converted_associated_type) = converted_associated_type else { return false; @@ -271,6 +292,7 @@ pub(crate) fn build_index<'tcx>( associated_types, lastpathid, crate_paths, + tcx, ); } true @@ -288,6 +310,7 @@ pub(crate) fn build_index<'tcx>( associated_types, lastpathid, crate_paths, + tcx, ); } if let Some(search_type) = &mut item.search_type { @@ -300,6 +323,7 @@ pub(crate) fn build_index<'tcx>( &mut associated_types, &mut lastpathid, &mut crate_paths, + tcx, ); } for item in &mut search_type.output { @@ -311,6 +335,7 @@ pub(crate) fn build_index<'tcx>( &mut associated_types, &mut lastpathid, &mut crate_paths, + tcx, ); } for constraint in &mut search_type.where_clause { @@ -323,6 +348,7 @@ pub(crate) fn build_index<'tcx>( &mut associated_types, &mut lastpathid, &mut crate_paths, + tcx, ); } } @@ -350,7 +376,12 @@ pub(crate) fn build_index<'tcx>( .filter(|exact_fqp| { exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp }); - crate_paths.push((short, fqp.clone(), exact_fqp.cloned())); + crate_paths.push(( + short, + fqp.clone(), + exact_fqp.cloned(), + utils::has_doc_flag(tcx, defid, sym::search_unbox), + )); Some(pathid) } else { None @@ -431,7 +462,7 @@ pub(crate) fn build_index<'tcx>( struct CrateData<'a> { items: Vec<&'a IndexItem>, - paths: Vec<(ItemType, Vec, Option>)>, + paths: Vec<(ItemType, Vec, Option>, bool)>, // The String is alias name and the vec is the list of the elements with this alias. // // To be noted: the `usize` elements are indexes to `items`. @@ -450,6 +481,7 @@ pub(crate) fn build_index<'tcx>( name: Symbol, path: Option, exact_path: Option, + search_unbox: bool, } impl Serialize for Paths { @@ -467,6 +499,15 @@ pub(crate) fn build_index<'tcx>( assert!(self.path.is_some()); seq.serialize_element(path)?; } + if self.search_unbox { + if self.path.is_none() { + seq.serialize_element(&None::)?; + } + if self.exact_path.is_none() { + seq.serialize_element(&None::)?; + } + seq.serialize_element(&1)?; + } seq.end() } } @@ -489,9 +530,15 @@ pub(crate) fn build_index<'tcx>( mod_paths.insert(&item.path, index); } let mut paths = Vec::with_capacity(self.paths.len()); - for (ty, path, exact) in &self.paths { + for &(ty, ref path, ref exact, search_unbox) in &self.paths { if path.len() < 2 { - paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None }); + paths.push(Paths { + ty, + name: path[0], + path: None, + exact_path: None, + search_unbox, + }); continue; } let full_path = join_with_double_colon(&path[..path.len() - 1]); @@ -517,10 +564,11 @@ pub(crate) fn build_index<'tcx>( }); if let Some(index) = mod_paths.get(&full_path) { paths.push(Paths { - ty: *ty, + ty, name: *path.last().unwrap(), path: Some(*index), exact_path, + search_unbox, }); continue; } @@ -532,10 +580,11 @@ pub(crate) fn build_index<'tcx>( match extra_paths.entry(full_path.clone()) { Entry::Occupied(entry) => { paths.push(Paths { - ty: *ty, + ty, name: *path.last().unwrap(), path: Some(*entry.get()), exact_path, + search_unbox, }); } Entry::Vacant(entry) => { @@ -544,10 +593,11 @@ pub(crate) fn build_index<'tcx>( revert_extra_paths.insert(index, full_path); } paths.push(Paths { - ty: *ty, + ty, name: *path.last().unwrap(), path: Some(index), exact_path, + search_unbox, }); } } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 0458b81d3525..d269c9322a31 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1327,6 +1327,7 @@ class DocSearch { exactPath: null, generics, bindings, + unboxFlag: true, }; } else if (pathIndex === 0) { // `0` is used as a sentinel because it's fewer bytes than `null` @@ -1338,6 +1339,7 @@ class DocSearch { exactPath: null, generics, bindings, + unboxFlag: true, }; } else { const item = lowercasePaths[pathIndex - 1]; @@ -1353,6 +1355,7 @@ class DocSearch { exactPath: item.exactPath, generics, bindings, + unboxFlag: item.unboxFlag, }; } const cr = this.TYPES_POOL.get(result.id); @@ -1390,6 +1393,7 @@ class DocSearch { if (cr.ty === result.ty && cr.path === result.path && cr.bindings === result.bindings && cr.generics === result.generics && cr.ty === result.ty && cr.name === result.name + && cr.unboxFlag === result.unboxFlag ) { return cr; } @@ -1681,14 +1685,17 @@ class DocSearch { const ty = elem[0]; const name = elem[1]; let path = null; - if (elem.length > 2) { + if (elem.length > 2 && elem[2] !== null) { path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath; lastPath = path; } - const exactPath = elem.length > 3 ? itemPaths.get(elem[3]) : path; + const exactPath = elem.length > 3 && elem[3] !== null ? + itemPaths.get(elem[3]) : + path; + const unboxFlag = elem.length > 4 && !!elem[4]; - lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath }); - paths[i] = { ty, name, path, exactPath }; + lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath, unboxFlag }); + paths[i] = { ty, name, path, exactPath, unboxFlag }; } // convert `item*` into an object form, and construct word indices. @@ -2376,17 +2383,20 @@ class DocSearch { */ const writeFn = (fnType, result) => { if (fnType.id < 0) { - const queryId = mgens && mgens.has(fnType.id) ? mgens.get(fnType.id) : null; if (fnParamNames[-1 - fnType.id] === "") { for (const nested of fnType.generics) { writeFn(nested, result); } return; - } else if (queryId < 0) { - mappedNames.set( - fnParamNames[-1 - fnType.id], - queryParamNames[-1 - queryId], - ); + } else if (mgens) { + for (const [queryId, fnId] of mgens) { + if (fnId === fnType.id) { + mappedNames.set( + queryParamNames[-1 - queryId], + fnParamNames[-1 - fnType.id], + ); + } + } } pushText({ name: fnParamNames[-1 - fnType.id], @@ -2436,15 +2446,15 @@ class DocSearch { } pushText({ name, highlighted: false }, result); pushText({ - name: values.length > 1 ? "=(" : "=", + name: values.length !== 1 ? "=(" : "=", highlighted: false, }, result); onEachBtwn( - values, + values || [], value => writeFn(value, result), () => pushText({ name: " + ", highlighted: false }, result), ); - if (values.length > 1) { + if (values.length !== 1) { pushText({ name: ")", highlighted: false }, result); } }, @@ -2616,8 +2626,8 @@ class DocSearch { * @param {Array} queryElems - The elements from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. * @param {Map|null} mgensIn - * - Map functions generics to query generics (never modified). - * @param {null|Map -> bool} solutionCb - Called for each `mgens` solution. + * - Map query generics to function generics (never modified). + * @param {Map -> bool} solutionCb - Called for each `mgens` solution. * @param {number} unboxingDepth * - Limit checks that Ty matches Vec, * but not Vec>>>> @@ -2640,7 +2650,7 @@ class DocSearch { */ const mgens = mgensIn === null ? null : new Map(mgensIn); if (queryElems.length === 0) { - return (!solutionCb || solutionCb(mgens)) ? fnTypesIn : null; + return solutionCb(mgens) ? fnTypesIn : null; } if (!fnTypesIn || fnTypesIn.length === 0) { return null; @@ -2657,12 +2667,12 @@ class DocSearch { continue; } if (fnType.id < 0 && queryElem.id < 0) { - if (mgens && mgens.has(fnType.id) && - mgens.get(fnType.id) !== queryElem.id) { + if (mgens && mgens.has(queryElem.id) && + mgens.get(queryElem.id) !== fnType.id) { continue; } const mgensScratch = new Map(mgens); - mgensScratch.set(fnType.id, queryElem.id); + mgensScratch.set(queryElem.id, fnType.id); if (!solutionCb || solutionCb(mgensScratch)) { const highlighted = [...fnTypesIn]; highlighted[i] = Object.assign({ @@ -2672,13 +2682,13 @@ class DocSearch { }); return highlighted; } - } else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) { + } else if (solutionCb(mgens ? new Map(mgens) : null)) { // unifyFunctionTypeIsMatchCandidate already checks that ids match const highlighted = [...fnTypesIn]; highlighted[i] = Object.assign({ highlighted: true, }, fnType, { - generics: unifyFunctionTypes( + generics: unifyGenericTypes( fnType.generics, queryElem.generics, whereClause, @@ -2701,17 +2711,11 @@ class DocSearch { continue; } if (fnType.id < 0) { - if (mgens && mgens.has(fnType.id) && - mgens.get(fnType.id) !== 0) { - continue; - } - const mgensScratch = new Map(mgens); - mgensScratch.set(fnType.id, 0); const highlightedGenerics = unifyFunctionTypes( whereClause[(-fnType.id) - 1], queryElems, whereClause, - mgensScratch, + mgens, solutionCb, unboxingDepth + 1, ); @@ -2782,11 +2786,11 @@ class DocSearch { let mgensScratch; if (fnType.id < 0) { mgensScratch = new Map(mgens); - if (mgensScratch.has(fnType.id) - && mgensScratch.get(fnType.id) !== queryElem.id) { + if (mgensScratch.has(queryElem.id) + && mgensScratch.get(queryElem.id) !== fnType.id) { continue; } - mgensScratch.set(fnType.id, queryElem.id); + mgensScratch.set(queryElem.id, fnType.id); } else { mgensScratch = mgens; } @@ -2809,7 +2813,7 @@ class DocSearch { mgensScratch => { if (fnType.generics.length === 0 && queryElem.generics.length === 0 && fnType.bindings.size === 0 && queryElem.bindings.size === 0) { - return !solutionCb || solutionCb(mgensScratch); + return solutionCb(mgensScratch); } const solution = unifyFunctionTypeCheckBindings( fnType, @@ -2823,7 +2827,7 @@ class DocSearch { } const simplifiedGenerics = solution.simplifiedGenerics; for (const simplifiedMgens of solution.mgens) { - unifiedGenerics = unifyFunctionTypes( + unifiedGenerics = unifyGenericTypes( simplifiedGenerics, queryElem.generics, whereClause, @@ -2831,7 +2835,7 @@ class DocSearch { solutionCb, unboxingDepth, ); - if (unifiedGenerics) { + if (unifiedGenerics !== null) { unifiedGenericsMgens = simplifiedMgens; return true; } @@ -2875,16 +2879,6 @@ class DocSearch { )) { continue; } - let mgensScratch; - if (fnType.id < 0) { - mgensScratch = new Map(mgens); - if (mgensScratch.has(fnType.id) && mgensScratch.get(fnType.id) !== 0) { - continue; - } - mgensScratch.set(fnType.id, 0); - } else { - mgensScratch = mgens; - } const generics = fnType.id < 0 ? whereClause[(-fnType.id) - 1] : fnType.generics; @@ -2895,7 +2889,7 @@ class DocSearch { fnTypes.toSpliced(i, 1, ...bindings, ...generics), queryElems, whereClause, - mgensScratch, + mgens, solutionCb, unboxingDepth + 1, ); @@ -2919,6 +2913,199 @@ class DocSearch { } return null; } + /** + * This function compares two lists of generics. + * + * This function behaves very similarly to `unifyFunctionTypes`, except that it + * doesn't skip or reorder anything. This is intended to match the behavior of + * the ordinary Rust type system, so that `Vec` only matches an actual + * `Vec` of `Allocators` and not the implicit `Allocator` parameter that every + * `Vec` has. + * + * @param {Array} fnTypesIn - The objects to check. + * @param {Array} queryElems - The elements from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map|null} mgensIn + * - Map functions generics to query generics (never modified). + * @param {Map -> bool} solutionCb - Called for each `mgens` solution. + * @param {number} unboxingDepth + * - Limit checks that Ty matches Vec, + * but not Vec>>>> + * + * @return {[FunctionType]|null} - Returns highlighed results if a match, null otherwise. + */ + function unifyGenericTypes( + fnTypesIn, + queryElems, + whereClause, + mgensIn, + solutionCb, + unboxingDepth, + ) { + if (unboxingDepth >= UNBOXING_LIMIT) { + return null; + } + /** + * @type Map|null + */ + const mgens = mgensIn === null ? null : new Map(mgensIn); + if (queryElems.length === 0) { + return solutionCb(mgens) ? fnTypesIn : null; + } + if (!fnTypesIn || fnTypesIn.length === 0) { + return null; + } + const fnType = fnTypesIn[0]; + const queryElem = queryElems[0]; + if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) { + if (fnType.id < 0 && queryElem.id < 0) { + if (!mgens || !mgens.has(queryElem.id) || + mgens.get(queryElem.id) === fnType.id + ) { + const mgensScratch = new Map(mgens); + mgensScratch.set(queryElem.id, fnType.id); + const fnTypesRemaining = unifyGenericTypes( + fnTypesIn.slice(1), + queryElems.slice(1), + whereClause, + mgensScratch, + solutionCb, + unboxingDepth, + ); + if (fnTypesRemaining) { + const highlighted = [fnType, ...fnTypesRemaining]; + highlighted[0] = Object.assign({ + highlighted: true, + }, fnType, { + generics: whereClause[-1 - fnType.id], + }); + return highlighted; + } + } + } else { + let unifiedGenerics; + const fnTypesRemaining = unifyGenericTypes( + fnTypesIn.slice(1), + queryElems.slice(1), + whereClause, + mgens, + mgensScratch => { + const solution = unifyFunctionTypeCheckBindings( + fnType, + queryElem, + whereClause, + mgensScratch, + unboxingDepth, + ); + if (!solution) { + return false; + } + const simplifiedGenerics = solution.simplifiedGenerics; + for (const simplifiedMgens of solution.mgens) { + unifiedGenerics = unifyGenericTypes( + simplifiedGenerics, + queryElem.generics, + whereClause, + simplifiedMgens, + solutionCb, + unboxingDepth, + ); + if (unifiedGenerics !== null) { + return true; + } + } + }, + unboxingDepth, + ); + if (fnTypesRemaining) { + const highlighted = [fnType, ...fnTypesRemaining]; + highlighted[0] = Object.assign({ + highlighted: true, + }, fnType, { + generics: unifiedGenerics || fnType.generics, + }); + return highlighted; + } + } + } + if (unifyFunctionTypeIsUnboxCandidate( + fnType, + queryElem, + whereClause, + mgens, + unboxingDepth + 1, + )) { + let highlightedRemaining; + if (fnType.id < 0) { + // Where clause corresponds to `F: A + B` + // ^^^^^ + // The order of the constraints doesn't matter, so + // use order-agnostic matching for it. + const highlightedGenerics = unifyFunctionTypes( + whereClause[(-fnType.id) - 1], + [queryElem], + whereClause, + mgens, + mgensScratch => { + const hl = unifyGenericTypes( + fnTypesIn.slice(1), + queryElems.slice(1), + whereClause, + mgensScratch, + solutionCb, + unboxingDepth, + ); + if (hl) { + highlightedRemaining = hl; + } + return hl; + }, + unboxingDepth + 1, + ); + if (highlightedGenerics) { + return [Object.assign({ + highlighted: true, + }, fnType, { + generics: highlightedGenerics, + }), ...highlightedRemaining]; + } + } else { + const highlightedGenerics = unifyGenericTypes( + [ + ...Array.from(fnType.bindings.values()).flat(), + ...fnType.generics, + ], + [queryElem], + whereClause, + mgens, + mgensScratch => { + const hl = unifyGenericTypes( + fnTypesIn.slice(1), + queryElems.slice(1), + whereClause, + mgensScratch, + solutionCb, + unboxingDepth, + ); + if (hl) { + highlightedRemaining = hl; + } + return hl; + }, + unboxingDepth + 1, + ); + if (highlightedGenerics) { + return [Object.assign({}, fnType, { + generics: highlightedGenerics, + bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => { + return [k, highlightedGenerics.splice(0, v.length)]; + })), + }), ...highlightedRemaining]; + } + } + } + return null; + } /** * Check if this function is a match candidate. * @@ -2930,7 +3117,7 @@ class DocSearch { * * @param {FunctionType} fnType * @param {QueryElement} queryElem - * @param {Map|null} mgensIn - Map functions generics to query generics. + * @param {Map|null} mgensIn - Map query generics to function generics. * @returns {boolean} */ const unifyFunctionTypeIsMatchCandidate = (fnType, queryElem, mgensIn) => { @@ -2940,22 +3127,13 @@ class DocSearch { } // fnType.id < 0 means generic // queryElem.id < 0 does too - // mgensIn[fnType.id] = queryElem.id - // or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait - // and should make that same decision everywhere it appears + // mgensIn[queryElem.id] = fnType.id if (fnType.id < 0 && queryElem.id < 0) { - if (mgensIn) { - if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) { - return false; - } - for (const [fid, qid] of mgensIn.entries()) { - if (fnType.id !== fid && queryElem.id === qid) { - return false; - } - if (fnType.id === fid && queryElem.id !== qid) { - return false; - } - } + if ( + mgensIn && mgensIn.has(queryElem.id) && + mgensIn.get(queryElem.id) !== fnType.id + ) { + return false; } return true; } else { @@ -3032,7 +3210,7 @@ class DocSearch { * @param {FunctionType} fnType * @param {QueryElement} queryElem * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map} mgensIn - Map functions generics to query generics. + * @param {Map} mgensIn - Map query generics to function generics. * Never modified. * @param {number} unboxingDepth * @returns {false|{mgens: [Map], simplifiedGenerics: [FunctionType]}} @@ -3100,7 +3278,7 @@ class DocSearch { * @param {FunctionType} fnType * @param {QueryElement} queryElem * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgens - Map functions generics to query generics. + * @param {Map|null} mgens - Map query generics to function generics. * @param {number} unboxingDepth * @returns {boolean} */ @@ -3114,19 +3292,10 @@ class DocSearch { if (unboxingDepth >= UNBOXING_LIMIT) { return false; } - if (fnType.id < 0 && queryElem.id >= 0) { + if (fnType.id < 0) { if (!whereClause) { return false; } - // mgens[fnType.id] === 0 indicates that we committed to unboxing this generic - // mgens[fnType.id] === null indicates that we haven't decided yet - if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { - return false; - } - // Where clauses can represent cyclical data. - // `null` prevents it from trying to unbox in an infinite loop - const mgensTmp = new Map(mgens); - mgensTmp.set(fnType.id, null); // This is only a potential unbox if the search query appears in the where clause // for example, searching `Read -> usize` should find // `fn read_all(R) -> Result` @@ -3135,10 +3304,11 @@ class DocSearch { whereClause[(-fnType.id) - 1], queryElem, whereClause, - mgensTmp, + mgens, unboxingDepth, ); - } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) { + } else if (fnType.unboxFlag && + (fnType.generics.length > 0 || fnType.bindings.size > 0)) { const simplifiedGenerics = [ ...fnType.generics, ...Array.from(fnType.bindings.values()).flat(), @@ -3182,7 +3352,7 @@ class DocSearch { * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgens - Map functions generics to query generics. + * @param {Map|null} mgens - Map query generics to function generics. * * @return {boolean} - Returns true if the type matches, false otherwise. */ @@ -3200,10 +3370,34 @@ class DocSearch { ) { return row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty); } else { - return unifyFunctionTypes([row], [elem], whereClause, mgens, null, unboxingDepth); + return unifyFunctionTypes( + [row], + [elem], + whereClause, + mgens, + () => true, + unboxingDepth, + ); } }; + /** + * Check a query solution for conflicting generics. + */ + const checkTypeMgensForConflict = mgens => { + if (!mgens) { + return true; + } + const fnTypes = new Set(); + for (const [_qid, fid] of mgens) { + if (fnTypes.has(fid)) { + return false; + } + fnTypes.add(fid); + } + return true; + }; + /** * Compute an "edit distance" that ignores missing path elements. * @param {string[]} contains search query path @@ -3523,7 +3717,7 @@ class DocSearch { parsedQuery.returned, row.type.where_clause, mgens, - null, + checkTypeMgensForConflict, 0, // unboxing depth ); }, @@ -3973,7 +4167,7 @@ ${item.displayPath}${name}\ displayType.appendChild(line); addWhereLineFn = () => {}; }; - for (const [name, qname] of mappedNames) { + for (const [qname, name] of mappedNames) { // don't care unless the generic name is different if (name === qname) { continue; diff --git a/tests/rustdoc-gui/search-about-this-result.goml b/tests/rustdoc-gui/search-about-this-result.goml index 62780d01ed7e..1d45c06dc43d 100644 --- a/tests/rustdoc-gui/search-about-this-result.goml +++ b/tests/rustdoc-gui/search-about-this-result.goml @@ -11,8 +11,8 @@ assert-count: ("#search-tabs button", 1) assert-count: (".search-results > a", 1) assert: "//div[@class='type-signature']/strong[text()='Iterator']" -assert: "//div[@class='type-signature']/strong[text()='(']" -assert: "//div[@class='type-signature']/strong[text()=')']" +assert: "//div[@class='type-signature']/strong[text()='(B']" +assert: "//div[@class='type-signature']/strong[text()='B)']" assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='FnMut']" assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Iterator::Item']" diff --git a/tests/rustdoc-js-std/bufread-fill-buf.js b/tests/rustdoc-js-std/bufread-fill-buf.js index 3828cf76026e..6b9309f68640 100644 --- a/tests/rustdoc-js-std/bufread-fill-buf.js +++ b/tests/rustdoc-js-std/bufread-fill-buf.js @@ -2,12 +2,15 @@ const EXPECTED = [ { - 'query': 'bufread -> result', + 'query': 'bufread -> result<[u8]>', + 'others': [ + { 'path': 'std::boxed::Box', 'name': 'fill_buf' }, + ], + }, + { + 'query': 'split -> option>>', 'others': [ { 'path': 'std::io::Split', 'name': 'next' }, - { 'path': 'std::boxed::Box', 'name': 'fill_buf' }, - { 'path': 'std::io::Chain', 'name': 'fill_buf' }, - { 'path': 'std::io::Take', 'name': 'fill_buf' }, ], }, ]; diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js index 1690d5dc8b5b..3be18e6adf32 100644 --- a/tests/rustdoc-js-std/option-type-signatures.js +++ b/tests/rustdoc-js-std/option-type-signatures.js @@ -80,11 +80,6 @@ const EXPECTED = [ 'name': 'and', 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>', }, - { - 'path': 'std::option::Option', - 'name': 'zip', - 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(T, `U`)>', - }, ], }, { @@ -103,12 +98,12 @@ const EXPECTED = [ ], }, { - 'query': 'option, option -> option', + 'query': 'option, option -> option<(t, u)>', 'others': [ { 'path': 'std::option::Option', 'name': 'zip', - 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, `U`)>', + 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`(T`, `U)`>', }, ], }, @@ -174,37 +169,23 @@ const EXPECTED = [ 'path': 'std::option::Option', 'name': 'map', 'displayType': '`Option`<`T`>, F -> `Option`', - 'displayMappedNames': `T = t, U = u`, + 'displayMappedNames': `t = T, u = U`, 'displayWhereClause': "F: `FnOnce` (T) -> `U`", }, { 'path': 'std::option::Option', 'name': 'and_then', 'displayType': '`Option`<`T`>, F -> `Option`', - 'displayMappedNames': `T = t, U = u`, + 'displayMappedNames': `t = T, u = U`, 'displayWhereClause': "F: `FnOnce` (T) -> Option<`U`>", }, { 'path': 'std::option::Option', 'name': 'zip_with', 'displayType': 'Option, `Option`<`U`>, F -> `Option`', - 'displayMappedNames': `U = t, R = u`, + 'displayMappedNames': `t = U, u = R`, 'displayWhereClause': "F: `FnOnce` (T, U) -> `R`", }, - { - 'path': 'std::task::Poll', - 'name': 'map_ok', - 'displayType': 'Poll<`Option`>>, F -> Poll<`Option`>>', - 'displayMappedNames': `T = t, U = u`, - 'displayWhereClause': "F: `FnOnce` (T) -> `U`", - }, - { - 'path': 'std::task::Poll', - 'name': 'map_err', - 'displayType': 'Poll<`Option`>>, F -> Poll<`Option`>>', - 'displayMappedNames': `T = t, U = u`, - 'displayWhereClause': "F: `FnOnce` (E) -> `U`", - }, ], }, { @@ -214,7 +195,7 @@ const EXPECTED = [ 'path': 'std::option::Option', 'name': 'and_then', 'displayType': '`Option`<`T`>, F -> `Option`', - 'displayMappedNames': `T = t, U = u`, + 'displayMappedNames': `t = T, u = U`, 'displayWhereClause': "F: `FnOnce` (T) -> `Option`<`U`>", }, ], diff --git a/tests/rustdoc-js-std/vec-type-signatures.js b/tests/rustdoc-js-std/vec-type-signatures.js index 18cf9d6efd0f..3c2ff30a8333 100644 --- a/tests/rustdoc-js-std/vec-type-signatures.js +++ b/tests/rustdoc-js-std/vec-type-signatures.js @@ -19,4 +19,16 @@ const EXPECTED = [ { 'path': 'std::vec::IntoIter', 'name': 'next_chunk' }, ], }, + { + 'query': 'vec -> Box<[T]>', + 'others': [ + { + 'path': 'std::boxed::Box', + 'name': 'from', + 'displayType': '`Vec`<`T`, `A`> -> `Box`<`[T]`, A>', + 'displayMappedNames': `T = T`, + 'displayWhereClause': 'A: `Allocator`', + }, + ], + }, ]; diff --git a/tests/rustdoc-js/assoc-type-backtrack.js b/tests/rustdoc-js/assoc-type-backtrack.js index 493e1a9910d1..ccf5c063c8c5 100644 --- a/tests/rustdoc-js/assoc-type-backtrack.js +++ b/tests/rustdoc-js/assoc-type-backtrack.js @@ -6,7 +6,6 @@ const EXPECTED = [ 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' }, - { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, ], }, { @@ -14,6 +13,19 @@ const EXPECTED = [ 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' }, + ], + }, + { + 'query': 'cloned, mytrait2 -> T', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, + ], + }, + { + 'query': 'cloned>, mytrait2 -> T', + 'correction': null, + 'others': [ { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, ], }, @@ -22,7 +34,6 @@ const EXPECTED = [ 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' }, - { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, ], }, { @@ -50,14 +61,14 @@ const EXPECTED = [ ], }, { - 'query': 'mytrait -> Option', + 'query': 'cloned> -> Option', 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' }, ], }, { - 'query': 'mytrait -> Option', + 'query': 'cloned> -> Option', 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' }, @@ -89,19 +100,21 @@ const EXPECTED = [ ], }, { - 'query': 'myintofuture> -> myfuture', + 'query': 'myintofuture> -> myfuture', 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, ], }, - // Invalid unboxing of the one-argument case. - // If you unbox one of the myfutures, you need to unbox both of them. + // Unboxings of the one-argument case. { 'query': 'myintofuture -> myfuture', 'correction': null, - 'others': [], + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], }, // Unboxings of the two-argument case. { @@ -119,7 +132,7 @@ const EXPECTED = [ ], }, { - 'query': 'myintofuture, myintofuture -> myfuture', + 'query': 'myintofuture, myintofuture -> myfuture', 'correction': null, 'others': [ { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, @@ -132,24 +145,29 @@ const EXPECTED = [ { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, ], }, - // Invalid unboxings of the two-argument case. - // If you unbox one of the myfutures, you need to unbox all of them. + // If you unbox one of the myfutures, you don't need to unbox all of them. { 'query': 'myintofuture, myintofuture> -> myfuture', 'correction': null, - 'others': [], + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], }, { 'query': 'myintofuture>, myintofuture -> myfuture', 'correction': null, - 'others': [], + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], }, { 'query': 'myintofuture>, myintofuture> -> t', 'correction': null, - 'others': [], + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], }, - // different generics don't match up either + // different generics must match up { 'query': 'myintofuture>, myintofuture> -> myfuture', 'correction': null, diff --git a/tests/rustdoc-js/assoc-type-backtrack.rs b/tests/rustdoc-js/assoc-type-backtrack.rs index 2dfede9dc383..8a74685b30bb 100644 --- a/tests/rustdoc-js/assoc-type-backtrack.rs +++ b/tests/rustdoc-js/assoc-type-backtrack.rs @@ -1,3 +1,5 @@ +#![feature(rustdoc_internals)] + pub trait MyTrait2 { type Output; } @@ -31,10 +33,12 @@ where } } +#[doc(search_unbox)] pub trait MyFuture { type Output; } +#[doc(search_unbox)] pub trait MyIntoFuture { type Output; type Fut: MyFuture; diff --git a/tests/rustdoc-js/assoc-type-unbound.js b/tests/rustdoc-js/assoc-type-unbound.js index 611b8bd1501c..9fd14f9d9fb5 100644 --- a/tests/rustdoc-js/assoc-type-unbound.js +++ b/tests/rustdoc-js/assoc-type-unbound.js @@ -13,7 +13,7 @@ const EXPECTED = [ 'path': 'assoc_type_unbound::MyIter', 'name': 'next', 'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>', - 'displayMappedNames': 'MyIter::Item = T', + 'displayMappedNames': 'T = MyIter::Item', 'displayWhereClause': '', }, ], @@ -26,7 +26,7 @@ const EXPECTED = [ 'path': 'assoc_type_unbound::MyIter', 'name': 'next', 'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>', - 'displayMappedNames': 'MyIter::Item = T', + 'displayMappedNames': 'T = MyIter::Item', 'displayWhereClause': '', }, ], diff --git a/tests/rustdoc-js/assoc-type.rs b/tests/rustdoc-js/assoc-type.rs index e12e73cb546d..aee8f4b1c3fa 100644 --- a/tests/rustdoc-js/assoc-type.rs +++ b/tests/rustdoc-js/assoc-type.rs @@ -1,12 +1,22 @@ -pub fn my_fn>(_x: X) -> u32 { +#![feature(rustdoc_internals)] + +pub fn my_fn>(_x: X) -> u32 { 3 } pub struct Something; pub mod my { + #[doc(search_unbox)] pub trait Iterator {} pub fn other_fn>(_: X) -> u32 { 3 } } + +pub mod other { + #[doc(search_unbox)] + pub trait Iterator { + type Item; + } +} diff --git a/tests/rustdoc-js/generics-impl.js b/tests/rustdoc-js/generics-impl.js index 5e33e224876f..c104730dcbd5 100644 --- a/tests/rustdoc-js/generics-impl.js +++ b/tests/rustdoc-js/generics-impl.js @@ -14,7 +14,7 @@ const EXPECTED = [ ], }, { - 'query': 'Aaaaaaa -> usize', + 'query': 'Aaaaaaa -> Result', 'others': [ { 'path': 'generics_impl::Aaaaaaa', 'name': 'read' }, ], @@ -23,6 +23,11 @@ const EXPECTED = [ 'query': 'Read -> u64', 'others': [ { 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' }, + ], + }, + { + 'query': 'Ddddddd -> u64', + 'others': [ { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, ], }, @@ -30,7 +35,6 @@ const EXPECTED = [ 'query': 'trait:Read -> u64', 'others': [ { 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' }, - { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, ], }, { diff --git a/tests/rustdoc-js/generics-impl.rs b/tests/rustdoc-js/generics-impl.rs index 27d44fdd7e92..f9fe7f390f3c 100644 --- a/tests/rustdoc-js/generics-impl.rs +++ b/tests/rustdoc-js/generics-impl.rs @@ -1,4 +1,4 @@ -use std::io::{Read, Result as IoResult}; +use std::io::{self, Read}; pub struct Aaaaaaa; @@ -12,7 +12,7 @@ impl Aaaaaaa { } impl Read for Aaaaaaa { - fn read(&mut self, out: &mut [u8]) -> IoResult { + fn read(&mut self, out: &mut [u8]) -> io::Result { Ok(out.len()) } } diff --git a/tests/rustdoc-js/generics-match-ambiguity-no-unbox.js b/tests/rustdoc-js/generics-match-ambiguity-no-unbox.js new file mode 100644 index 000000000000..ea4c26d311c3 --- /dev/null +++ b/tests/rustdoc-js/generics-match-ambiguity-no-unbox.js @@ -0,0 +1,68 @@ +// ignore-order +// exact-check + +// Make sure that results are order-agnostic, even when there's search items that only differ +// by generics. + +const EXPECTED = [ + { + 'query': 'Wrap', + 'in_args': [ + { 'path': 'generics_match_ambiguity', 'name': 'bar' }, + { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + ], + }, + { + 'query': 'Wrap', + 'in_args': [ + { 'path': 'generics_match_ambiguity', 'name': 'bar' }, + { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + ], + }, + { + 'query': 'Wrap, Wrap', + 'others': [ + { 'path': 'generics_match_ambiguity', 'name': 'bar' }, + { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + ], + }, + { + 'query': 'Wrap, Wrap', + 'others': [ + { 'path': 'generics_match_ambiguity', 'name': 'bar' }, + { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + ], + }, + { + 'query': 'W3, W3', + 'others': [ + { 'path': 'generics_match_ambiguity', 'name': 'baaa' }, + { 'path': 'generics_match_ambiguity', 'name': 'baab' }, + ], + }, + { + 'query': 'W3, W3', + 'others': [ + { 'path': 'generics_match_ambiguity', 'name': 'baaa' }, + { 'path': 'generics_match_ambiguity', 'name': 'baab' }, + ], + }, + { + // strict generics matching; W2 doesn't match W2>, + // even though W2 works just fine (ignoring the W3) + 'query': 'W2, W2', + 'others': [], + }, + { + 'query': 'W2, W2', + 'others': [], + }, + { + 'query': 'W2, W3', + 'others': [], + }, + { + 'query': 'W2, W2', + 'others': [], + }, +]; diff --git a/tests/rustdoc-js/generics-match-ambiguity-no-unbox.rs b/tests/rustdoc-js/generics-match-ambiguity-no-unbox.rs new file mode 100644 index 000000000000..43c2896fa2cd --- /dev/null +++ b/tests/rustdoc-js/generics-match-ambiguity-no-unbox.rs @@ -0,0 +1,18 @@ +#![crate_name = "generics_match_ambiguity"] + +pub struct Wrap(pub T, pub U); + +pub fn foo(a: Wrap, b: Wrap) {} +pub fn bar(a: Wrap, b: Wrap) {} + +pub struct W2(pub T); +pub struct W3(pub T, pub U); + +pub fn baaa(a: W3, b: W3) {} +pub fn baab(a: W3, b: W3) {} +pub fn baac(a: W2>, b: W3) {} +pub fn baad(a: W2>, b: W3) {} +pub fn baae(a: W3, b: W2>) {} +pub fn baaf(a: W3, b: W2>) {} +pub fn baag(a: W2>, b: W2>) {} +pub fn baah(a: W2>, b: W2>) {} diff --git a/tests/rustdoc-js/generics-match-ambiguity.js b/tests/rustdoc-js/generics-match-ambiguity.js index edce4268c5ac..aadb179321cf 100644 --- a/tests/rustdoc-js/generics-match-ambiguity.js +++ b/tests/rustdoc-js/generics-match-ambiguity.js @@ -60,18 +60,14 @@ const EXPECTED = [ ], }, { + // strict generics matching; W2 doesn't match W2>, + // even though W2 works just fine (ignoring the W3) 'query': 'W2, W2', - 'others': [ - { 'path': 'generics_match_ambiguity', 'name': 'baag' }, - { 'path': 'generics_match_ambiguity', 'name': 'baah' }, - ], + 'others': [], }, { 'query': 'W2, W2', - 'others': [ - { 'path': 'generics_match_ambiguity', 'name': 'baag' }, - { 'path': 'generics_match_ambiguity', 'name': 'baah' }, - ], + 'others': [], }, { 'query': 'W2, W3', diff --git a/tests/rustdoc-js/generics-match-ambiguity.rs b/tests/rustdoc-js/generics-match-ambiguity.rs index 79c493856ebc..7aadbbd609ca 100644 --- a/tests/rustdoc-js/generics-match-ambiguity.rs +++ b/tests/rustdoc-js/generics-match-ambiguity.rs @@ -1,9 +1,14 @@ +#![feature(rustdoc_internals)] + +#[doc(search_unbox)] pub struct Wrap(pub T, pub U); pub fn foo(a: Wrap, b: Wrap) {} pub fn bar(a: Wrap, b: Wrap) {} +#[doc(search_unbox)] pub struct W2(pub T); +#[doc(search_unbox)] pub struct W3(pub T, pub U); pub fn baaa(a: W3, b: W3) {} @@ -14,4 +19,3 @@ pub fn baae(a: W3, b: W2>) {} pub fn baaf(a: W3, b: W2>) {} pub fn baag(a: W2>, b: W2>) {} pub fn baah(a: W2>, b: W2>) {} -// diff --git a/tests/rustdoc-js/generics-nested.js b/tests/rustdoc-js/generics-nested.js index 294c19490748..b3184dde0d0c 100644 --- a/tests/rustdoc-js/generics-nested.js +++ b/tests/rustdoc-js/generics-nested.js @@ -18,9 +18,8 @@ const EXPECTED = [ ], }, { + // can't put generics out of order 'query': '-> Out', - 'others': [ - { 'path': 'generics_nested', 'name': 'bet' }, - ], + 'others': [], }, ]; diff --git a/tests/rustdoc-js/generics-unbox.js b/tests/rustdoc-js/generics-unbox.js index 9cdfc7ac8b6e..6baf00c814bb 100644 --- a/tests/rustdoc-js/generics-unbox.js +++ b/tests/rustdoc-js/generics-unbox.js @@ -11,20 +11,17 @@ const EXPECTED = [ 'query': 'Inside -> Out3', 'others': [ { 'path': 'generics_unbox', 'name': 'beta' }, - { 'path': 'generics_unbox', 'name': 'gamma' }, ], }, { 'query': 'Inside -> Out4', 'others': [ - { 'path': 'generics_unbox', 'name': 'beta' }, { 'path': 'generics_unbox', 'name': 'gamma' }, ], }, { 'query': 'Inside -> Out3', 'others': [ - { 'path': 'generics_unbox', 'name': 'beta' }, { 'path': 'generics_unbox', 'name': 'gamma' }, ], }, @@ -32,7 +29,6 @@ const EXPECTED = [ 'query': 'Inside -> Out4', 'others': [ { 'path': 'generics_unbox', 'name': 'beta' }, - { 'path': 'generics_unbox', 'name': 'gamma' }, ], }, ]; diff --git a/tests/rustdoc-js/generics-unbox.rs b/tests/rustdoc-js/generics-unbox.rs index bef34f891e95..c2578575997b 100644 --- a/tests/rustdoc-js/generics-unbox.rs +++ b/tests/rustdoc-js/generics-unbox.rs @@ -1,26 +1,34 @@ +#![feature(rustdoc_internals)] + +#[doc(search_unbox)] pub struct Out { a: A, b: B, } +#[doc(search_unbox)] pub struct Out1 { a: [A; N], } +#[doc(search_unbox)] pub struct Out2 { a: [A; N], } +#[doc(search_unbox)] pub struct Out3 { a: A, b: B, } +#[doc(search_unbox)] pub struct Out4 { a: A, b: B, } +#[doc(search_unbox)] pub struct Inside(T); pub fn alpha(_: Inside) -> Out, Out2> { diff --git a/tests/rustdoc-js/generics.js b/tests/rustdoc-js/generics.js index b3ca0af3056a..a6d20538efee 100644 --- a/tests/rustdoc-js/generics.js +++ b/tests/rustdoc-js/generics.js @@ -30,21 +30,13 @@ const EXPECTED = [ 'others': [ { 'path': 'generics', 'name': 'P' }, ], - 'returned': [ - { 'path': 'generics', 'name': 'alef' }, - ], - 'in_args': [ - { 'path': 'generics', 'name': 'alpha' }, - ], + 'returned': [], + 'in_args': [], }, { 'query': 'P', - 'returned': [ - { 'path': 'generics', 'name': 'alef' }, - ], - 'in_args': [ - { 'path': 'generics', 'name': 'alpha' }, - ], + 'returned': [], + 'in_args': [], }, { 'query': '"ExtraCreditStructMulti"', diff --git a/tests/rustdoc-js/hof.js b/tests/rustdoc-js/hof.js index 5e6c9d83c7c7..c1142f106688 100644 --- a/tests/rustdoc-js/hof.js +++ b/tests/rustdoc-js/hof.js @@ -9,19 +9,19 @@ const EXPECTED = [ // ML-style higher-order function notation { - 'query': 'bool, (u32 -> !) -> ()', + 'query': 'bool, (first -> !) -> ()', 'others': [ {"path": "hof", "name": "fn_ptr"}, ], }, { - 'query': 'u8, (u32 -> !) -> ()', + 'query': 'u8, (second -> !) -> ()', 'others': [ {"path": "hof", "name": "fn_once"}, ], }, { - 'query': 'i8, (u32 -> !) -> ()', + 'query': 'i8, (third -> !) -> ()', 'others': [ {"path": "hof", "name": "fn_mut"}, ], @@ -54,9 +54,6 @@ const EXPECTED = [ 'query': '(u32 -> !) -> ()', 'others': [ {"path": "hof", "name": "fn_"}, - {"path": "hof", "name": "fn_ptr"}, - {"path": "hof", "name": "fn_mut"}, - {"path": "hof", "name": "fn_once"}, ], }, { @@ -95,30 +92,30 @@ const EXPECTED = [ // Rust-style higher-order function notation { - 'query': 'bool, fn(u32) -> ! -> ()', + 'query': 'bool, fn(first) -> ! -> ()', 'others': [ {"path": "hof", "name": "fn_ptr"}, ], }, { - 'query': 'u8, fnonce(u32) -> ! -> ()', + 'query': 'u8, fnonce(second) -> ! -> ()', 'others': [ {"path": "hof", "name": "fn_once"}, ], }, { - 'query': 'u8, fn(u32) -> ! -> ()', + 'query': 'u8, fn(second) -> ! -> ()', // fnonce != fn 'others': [], }, { - 'query': 'i8, fnmut(u32) -> ! -> ()', + 'query': 'i8, fnmut(third) -> ! -> ()', 'others': [ {"path": "hof", "name": "fn_mut"}, ], }, { - 'query': 'i8, fn(u32) -> ! -> ()', + 'query': 'i8, fn(third) -> ! -> ()', // fnmut != fn 'others': [], }, @@ -152,7 +149,7 @@ const EXPECTED = [ ], }, { - 'query': 'fn(u32) -> ! -> ()', + 'query': 'fn() -> ! -> ()', 'others': [ // fn matches primitive:fn and trait:Fn {"path": "hof", "name": "fn_"}, @@ -160,14 +157,14 @@ const EXPECTED = [ ], }, { - 'query': 'trait:fn(u32) -> ! -> ()', + 'query': 'trait:fn() -> ! -> ()', 'others': [ // fn matches primitive:fn and trait:Fn {"path": "hof", "name": "fn_"}, ], }, { - 'query': 'primitive:fn(u32) -> ! -> ()', + 'query': 'primitive:fn() -> ! -> ()', 'others': [ // fn matches primitive:fn and trait:Fn {"path": "hof", "name": "fn_ptr"}, diff --git a/tests/rustdoc-js/looks-like-rustc-interner.js b/tests/rustdoc-js/looks-like-rustc-interner.js index a4806d234992..d6d2764c3aed 100644 --- a/tests/rustdoc-js/looks-like-rustc-interner.js +++ b/tests/rustdoc-js/looks-like-rustc-interner.js @@ -1,9 +1,15 @@ // https://github.com/rust-lang/rust/pull/122247 // exact-check -const EXPECTED = { - 'query': 'canonicalvarinfo, intoiterator -> intoiterator', - 'others': [ - { 'path': 'looks_like_rustc_interner::Interner', 'name': 'mk_canonical_var_infos' }, - ], -}; +const EXPECTED = [ + { + 'query': 'canonicalvarinfo, intoiterator -> intoiterator', + 'others': [], + }, + { + 'query': '[canonicalvarinfo], interner -> intoiterator', + 'others': [ + { 'path': 'looks_like_rustc_interner::Interner', 'name': 'mk_canonical_var_infos' }, + ], + }, +]; diff --git a/tests/rustdoc-js/nested-unboxed.js b/tests/rustdoc-js/nested-unboxed.js index 44f784eb1f63..5f9eabc12f60 100644 --- a/tests/rustdoc-js/nested-unboxed.js +++ b/tests/rustdoc-js/nested-unboxed.js @@ -33,9 +33,8 @@ const EXPECTED = [ }, { 'query': '-> Result', - 'others': [ - { 'path': 'nested_unboxed', 'name': 'something' }, - ], + // can't put nested generics out of order + 'others': [], }, { 'query': '-> Result, bool>', @@ -45,9 +44,7 @@ const EXPECTED = [ }, { 'query': '-> Result, bool>', - 'others': [ - { 'path': 'nested_unboxed', 'name': 'something' }, - ], + 'others': [], }, { 'query': '-> Result, u32, bool>', diff --git a/tests/rustdoc-js/nested-unboxed.rs b/tests/rustdoc-js/nested-unboxed.rs index 57f9592b7914..6c8b1bd6aa14 100644 --- a/tests/rustdoc-js/nested-unboxed.rs +++ b/tests/rustdoc-js/nested-unboxed.rs @@ -1,3 +1,6 @@ +#![feature(rustdoc_internals)] + +#[doc(search_unbox)] pub struct Object(T, U); pub fn something() -> Result, bool> { diff --git a/tests/rustdoc-js/reference.js b/tests/rustdoc-js/reference.js index b4a1fb15d369..378fc03475b8 100644 --- a/tests/rustdoc-js/reference.js +++ b/tests/rustdoc-js/reference.js @@ -79,9 +79,8 @@ const EXPECTED = [ }, { 'query': 'reference, reference -> ()', - 'others': [ - { 'path': 'reference::Ring', 'name': 'wear' }, - ], + // can't leave out the `mut`, because can't reorder like that + 'others': [], }, { 'query': 'reference, reference -> ()', @@ -102,9 +101,8 @@ const EXPECTED = [ }, { 'query': 'reference, reference -> ()', - 'others': [ - { 'path': 'reference', 'name': 'show' }, - ], + // can't leave out the mut + 'others': [], }, { 'query': 'reference, reference -> ()', @@ -203,9 +201,8 @@ const EXPECTED = [ // middle with shorthand { 'query': '&middle, &middle -> ()', - 'others': [ - { 'path': 'reference', 'name': 'show' }, - ], + // can't leave out the mut + 'others': [], }, { 'query': '&mut middle, &mut middle -> ()', diff --git a/tests/rustdoc-js/tuple-unit.js b/tests/rustdoc-js/tuple-unit.js index d24a3da328c5..6a9b861cf948 100644 --- a/tests/rustdoc-js/tuple-unit.js +++ b/tests/rustdoc-js/tuple-unit.js @@ -57,7 +57,7 @@ const EXPECTED = [ 'in_args': [], }, { - 'query': '(Q, ())', + 'query': '(Q, R<()>)', 'returned': [ { 'path': 'tuple_unit', 'name': 'nest' }, ], @@ -71,7 +71,7 @@ const EXPECTED = [ 'in_args': [], }, { - 'query': '(u32)', + 'query': 'R<(u32)>', 'returned': [ { 'path': 'tuple_unit', 'name': 'nest' }, ], diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs index 58306a4cfc9c..57d6b5912870 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs @@ -7,4 +7,7 @@ trait Mine {} #[doc(fake_variadic)] //~ ERROR: `#[doc(fake_variadic)]` is meant for internal use only impl Mine for (T,) {} +#[doc(search_unbox)] //~ ERROR: `#[doc(search_unbox)]` is meant for internal use only +struct Wrap (T); + fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr index bbb9edd58f09..f3c00a2156bf 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr @@ -18,6 +18,16 @@ LL | #[doc(fake_variadic)] = help: add `#![feature(rustdoc_internals)]` 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 2 previous errors +error[E0658]: `#[doc(search_unbox)]` is meant for internal use only + --> $DIR/feature-gate-rustdoc_internals.rs:10:1 + | +LL | #[doc(search_unbox)] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #90418 for more information + = help: add `#![feature(rustdoc_internals)]` 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 3 previous errors For more information about this error, try `rustc --explain E0658`. From 9900ea48b566656fb12b5fcbd0a1b20aaa96e5ca Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 31 Oct 2024 13:09:37 -0700 Subject: [PATCH 018/214] Adjust ranking so that duplicates count against rank --- src/librustdoc/html/static/js/search.js | 25 +++++++++----------- tests/rustdoc-js-std/simd-type-signatures.js | 20 ++++++++-------- tests/rustdoc-js-std/transmute-fail.js | 13 ++++++++++ tests/rustdoc-js-std/transmute.js | 12 ++++++++++ tests/rustdoc-js/impl-trait.js | 6 ++--- tests/rustdoc-js/type-parameters.js | 6 ++--- 6 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 tests/rustdoc-js-std/transmute-fail.js create mode 100644 tests/rustdoc-js-std/transmute.js diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index d269c9322a31..4e1bbbbf59d8 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1412,7 +1412,7 @@ class DocSearch { * query fingerprint. If any bits are set in the query but not in the function, it can't * match. * - * - The fourth section has the number of distinct items in the set. + * - The fourth section has the number of items in the set. * This is the distance function, used for filtering and for sorting. * * [^1]: Distance is the relatively naive metric of counting the number of distinct items in @@ -1420,9 +1420,8 @@ class DocSearch { * * @param {FunctionType|QueryElement} type - a single type * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits - * @param {Set} fps - Set of distinct items */ - buildFunctionTypeFingerprint(type, output, fps) { + buildFunctionTypeFingerprint(type, output) { let input = type.id; // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter. // Differentiating between arrays and slices, if the user asks for it, is @@ -1468,10 +1467,11 @@ class DocSearch { output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32)); output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32)); output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32)); - fps.add(input); + // output[3] is the total number of items in the type signature + output[3] += 1; } for (const g of type.generics) { - this.buildFunctionTypeFingerprint(g, output, fps); + this.buildFunctionTypeFingerprint(g, output); } const fb = { id: null, @@ -1482,9 +1482,8 @@ class DocSearch { for (const [k, v] of type.bindings.entries()) { fb.id = k; fb.generics = v; - this.buildFunctionTypeFingerprint(fb, output, fps); + this.buildFunctionTypeFingerprint(fb, output); } - output[3] = fps.size; } /** @@ -1734,16 +1733,15 @@ class DocSearch { if (type !== null) { if (type) { const fp = this.functionTypeFingerprint.subarray(id * 4, (id + 1) * 4); - const fps = new Set(); for (const t of type.inputs) { - this.buildFunctionTypeFingerprint(t, fp, fps); + this.buildFunctionTypeFingerprint(t, fp); } for (const t of type.output) { - this.buildFunctionTypeFingerprint(t, fp, fps); + this.buildFunctionTypeFingerprint(t, fp); } for (const w of type.where_clause) { for (const t of w) { - this.buildFunctionTypeFingerprint(t, fp, fps); + this.buildFunctionTypeFingerprint(t, fp); } } } @@ -3885,14 +3883,13 @@ class DocSearch { ); }; - const fps = new Set(); for (const elem of parsedQuery.elems) { convertNameToId(elem); - this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps); + this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint); } for (const elem of parsedQuery.returned) { convertNameToId(elem); - this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps); + this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint); } if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) { diff --git a/tests/rustdoc-js-std/simd-type-signatures.js b/tests/rustdoc-js-std/simd-type-signatures.js index c07f15dcbe8b..4fc14e65ac42 100644 --- a/tests/rustdoc-js-std/simd-type-signatures.js +++ b/tests/rustdoc-js-std/simd-type-signatures.js @@ -20,11 +20,6 @@ const EXPECTED = [ 'name': 'simd_min', 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+N%3E/method.simd_min' }, - { - 'path': 'std::simd::prelude::Simd', - 'name': 'simd_clamp', - 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+N%3E/method.simd_clamp' - }, { 'path': 'std::simd::prelude::Simd', 'name': 'saturating_add', @@ -35,6 +30,11 @@ const EXPECTED = [ 'name': 'saturating_sub', 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci16,+N%3E/method.saturating_sub' }, + { + 'path': 'std::simd::prelude::Simd', + 'name': 'simd_clamp', + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+N%3E/method.simd_clamp' + }, ], }, { @@ -50,11 +50,6 @@ const EXPECTED = [ 'name': 'simd_min', 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+N%3E/method.simd_min' }, - { - 'path': 'std::simd::prelude::Simd', - 'name': 'simd_clamp', - 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+N%3E/method.simd_clamp' - }, { 'path': 'std::simd::prelude::Simd', 'name': 'saturating_add', @@ -65,6 +60,11 @@ const EXPECTED = [ 'name': 'saturating_sub', 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci8,+N%3E/method.saturating_sub' }, + { + 'path': 'std::simd::prelude::Simd', + 'name': 'simd_clamp', + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+N%3E/method.simd_clamp' + }, ], }, ]; diff --git a/tests/rustdoc-js-std/transmute-fail.js b/tests/rustdoc-js-std/transmute-fail.js new file mode 100644 index 000000000000..c4dddf3cf3ce --- /dev/null +++ b/tests/rustdoc-js-std/transmute-fail.js @@ -0,0 +1,13 @@ +// should-fail +const EXPECTED = [ + { + // Keep this test case identical to `transmute`, except the + // should-fail tag and the search query below: + 'query': 'generic:T -> generic:T', + 'others': [ + { 'path': 'std::intrinsics::simd', 'name': 'simd_as' }, + { 'path': 'std::intrinsics::simd', 'name': 'simd_cast' }, + { 'path': 'std::intrinsics', 'name': 'transmute' }, + ], + }, +]; diff --git a/tests/rustdoc-js-std/transmute.js b/tests/rustdoc-js-std/transmute.js new file mode 100644 index 000000000000..0e52e21e0dea --- /dev/null +++ b/tests/rustdoc-js-std/transmute.js @@ -0,0 +1,12 @@ +const EXPECTED = [ + { + // Keep this test case identical to `transmute-fail`, except the + // should-fail tag and the search query below: + 'query': 'generic:T -> generic:U', + 'others': [ + { 'path': 'std::intrinsics::simd', 'name': 'simd_as' }, + { 'path': 'std::intrinsics::simd', 'name': 'simd_cast' }, + { 'path': 'std::intrinsics', 'name': 'transmute' }, + ], + }, +]; diff --git a/tests/rustdoc-js/impl-trait.js b/tests/rustdoc-js/impl-trait.js index 8bb3f2d3e99a..3d7d0ca5bcda 100644 --- a/tests/rustdoc-js/impl-trait.js +++ b/tests/rustdoc-js/impl-trait.js @@ -23,8 +23,8 @@ const EXPECTED = [ 'others': [ { 'path': 'impl_trait', 'name': 'bbbbbbb' }, { 'path': 'impl_trait::Ccccccc', 'name': 'ddddddd' }, - { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, { 'path': 'impl_trait::Ccccccc', 'name': 'ggggggg' }, + { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, ], }, { @@ -39,14 +39,14 @@ const EXPECTED = [ { 'path': 'impl_trait', 'name': 'Aaaaaaa' }, ], 'in_args': [ - { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, { 'path': 'impl_trait::Ccccccc', 'name': 'eeeeeee' }, + { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, ], 'returned': [ { 'path': 'impl_trait', 'name': 'bbbbbbb' }, { 'path': 'impl_trait::Ccccccc', 'name': 'ddddddd' }, - { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, { 'path': 'impl_trait::Ccccccc', 'name': 'ggggggg' }, + { 'path': 'impl_trait::Ccccccc', 'name': 'fffffff' }, ], }, ]; diff --git a/tests/rustdoc-js/type-parameters.js b/tests/rustdoc-js/type-parameters.js index e045409e507e..fa2b8d2ebfdb 100644 --- a/tests/rustdoc-js/type-parameters.js +++ b/tests/rustdoc-js/type-parameters.js @@ -11,9 +11,9 @@ const EXPECTED = [ { query: '-> generic:T', others: [ - { path: 'foo', name: 'beta' }, { path: 'foo', name: 'bet' }, { path: 'foo', name: 'alef' }, + { path: 'foo', name: 'beta' }, ], }, { @@ -50,8 +50,8 @@ const EXPECTED = [ { query: 'generic:T', in_args: [ - { path: 'foo', name: 'beta' }, { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'beta' }, { path: 'foo', name: 'alternate' }, { path: 'foo', name: 'other' }, ], @@ -59,8 +59,8 @@ const EXPECTED = [ { query: 'generic:Other', in_args: [ - { path: 'foo', name: 'beta' }, { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'beta' }, { path: 'foo', name: 'alternate' }, { path: 'foo', name: 'other' }, ], From e8dfe6e4f255308f07e86a87e309912474cd27e5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 5 Oct 2024 20:39:15 +0200 Subject: [PATCH 019/214] float types: move copysign, abs, signum to libcore --- library/core/src/num/f128.rs | 98 ++++++++++++++++++++++++++++++++++++ library/core/src/num/f16.rs | 97 +++++++++++++++++++++++++++++++++++ library/core/src/num/f32.rs | 84 +++++++++++++++++++++++++++++++ library/core/src/num/f64.rs | 84 +++++++++++++++++++++++++++++++ library/std/src/f128.rs | 98 ------------------------------------ library/std/src/f16.rs | 97 ----------------------------------- library/std/src/f32.rs | 84 ------------------------------- library/std/src/f64.rs | 84 ------------------------------- 8 files changed, 363 insertions(+), 363 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index e8161cce2fe2..cbe1a6060de4 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1282,4 +1282,102 @@ impl f128 { } self } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # #[cfg(reliable_f128)] { + /// + /// let x = 3.5_f128; + /// let y = -3.5_f128; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f128::NAN.abs().is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f128", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn abs(self) -> Self { + // FIXME(f16_f128): replace with `intrinsics::fabsf128` when available + // We don't do this now because LLVM has lowering bugs for f128 math. + Self::from_bits(self.to_bits() & !(1 << 127)) + } + + /// Returns a number that represents the sign of `self`. + /// + /// - `1.0` if the number is positive, `+0.0` or `INFINITY` + /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` + /// - NaN if the number is NaN + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # #[cfg(reliable_f128_math)] { + /// + /// let f = 3.5_f128; + /// + /// assert_eq!(f.signum(), 1.0); + /// assert_eq!(f128::NEG_INFINITY.signum(), -1.0); + /// + /// assert!(f128::NAN.signum().is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f128", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn signum(self) -> f128 { + if self.is_nan() { Self::NAN } else { 1.0_f128.copysign(self) } + } + + /// Returns a number composed of the magnitude of `self` and the sign of + /// `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is + /// returned. + /// + /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note + /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust + /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the + /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable + /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more + /// info. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # #[cfg(reliable_f128_math)] { + /// + /// let f = 3.5_f128; + /// + /// assert_eq!(f.copysign(0.42), 3.5_f128); + /// assert_eq!(f.copysign(-0.42), -3.5_f128); + /// assert_eq!((-f).copysign(0.42), 3.5_f128); + /// assert_eq!((-f).copysign(-0.42), -3.5_f128); + /// + /// assert!(f128::NAN.copysign(1.0).is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f128", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn copysign(self, sign: f128) -> f128 { + unsafe { intrinsics::copysignf128(self, sign) } + } } diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 8b3f3b7d19bf..19263062c757 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1257,4 +1257,101 @@ impl f16 { } self } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(reliable_f16)] { + /// + /// let x = 3.5_f16; + /// let y = -3.5_f16; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f16::NAN.abs().is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f16", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn abs(self) -> Self { + // FIXME(f16_f128): replace with `intrinsics::fabsf16` when available + Self::from_bits(self.to_bits() & !(1 << 15)) + } + + /// Returns a number that represents the sign of `self`. + /// + /// - `1.0` if the number is positive, `+0.0` or `INFINITY` + /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` + /// - NaN if the number is NaN + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(reliable_f16_math)] { + /// + /// let f = 3.5_f16; + /// + /// assert_eq!(f.signum(), 1.0); + /// assert_eq!(f16::NEG_INFINITY.signum(), -1.0); + /// + /// assert!(f16::NAN.signum().is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f16", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn signum(self) -> f16 { + if self.is_nan() { Self::NAN } else { 1.0_f16.copysign(self) } + } + + /// Returns a number composed of the magnitude of `self` and the sign of + /// `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is + /// returned. + /// + /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note + /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust + /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the + /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable + /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more + /// info. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(reliable_f16_math)] { + /// + /// let f = 3.5_f16; + /// + /// assert_eq!(f.copysign(0.42), 3.5_f16); + /// assert_eq!(f.copysign(-0.42), -3.5_f16); + /// assert_eq!((-f).copysign(0.42), 3.5_f16); + /// assert_eq!((-f).copysign(-0.42), -3.5_f16); + /// + /// assert!(f16::NAN.copysign(1.0).is_nan()); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f16", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn copysign(self, sign: f16) -> f16 { + unsafe { intrinsics::copysignf16(self, sign) } + } } diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index a01761ee5d4a..d9337af316ce 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1427,4 +1427,88 @@ impl f32 { } self } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// let x = 3.5_f32; + /// let y = -3.5_f32; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f32::NAN.abs().is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[inline] + pub const fn abs(self) -> f32 { + unsafe { intrinsics::fabsf32(self) } + } + + /// Returns a number that represents the sign of `self`. + /// + /// - `1.0` if the number is positive, `+0.0` or `INFINITY` + /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` + /// - NaN if the number is NaN + /// + /// # Examples + /// + /// ``` + /// let f = 3.5_f32; + /// + /// assert_eq!(f.signum(), 1.0); + /// assert_eq!(f32::NEG_INFINITY.signum(), -1.0); + /// + /// assert!(f32::NAN.signum().is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[inline] + pub const fn signum(self) -> f32 { + if self.is_nan() { Self::NAN } else { 1.0_f32.copysign(self) } + } + + /// Returns a number composed of the magnitude of `self` and the sign of + /// `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is + /// returned. + /// + /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note + /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust + /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the + /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable + /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more + /// info. + /// + /// # Examples + /// + /// ``` + /// let f = 3.5_f32; + /// + /// assert_eq!(f.copysign(0.42), 3.5_f32); + /// assert_eq!(f.copysign(-0.42), -3.5_f32); + /// assert_eq!((-f).copysign(0.42), 3.5_f32); + /// assert_eq!((-f).copysign(-0.42), -3.5_f32); + /// + /// assert!(f32::NAN.copysign(1.0).is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[inline] + #[stable(feature = "copysign", since = "1.35.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + pub const fn copysign(self, sign: f32) -> f32 { + unsafe { intrinsics::copysignf32(self, sign) } + } } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 2995e41cd6ea..bcab5193718d 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1427,4 +1427,88 @@ impl f64 { } self } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// let x = 3.5_f64; + /// let y = -3.5_f64; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f64::NAN.abs().is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[inline] + pub const fn abs(self) -> f64 { + unsafe { intrinsics::fabsf64(self) } + } + + /// Returns a number that represents the sign of `self`. + /// + /// - `1.0` if the number is positive, `+0.0` or `INFINITY` + /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` + /// - NaN if the number is NaN + /// + /// # Examples + /// + /// ``` + /// let f = 3.5_f64; + /// + /// assert_eq!(f.signum(), 1.0); + /// assert_eq!(f64::NEG_INFINITY.signum(), -1.0); + /// + /// assert!(f64::NAN.signum().is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[inline] + pub const fn signum(self) -> f64 { + if self.is_nan() { Self::NAN } else { 1.0_f64.copysign(self) } + } + + /// Returns a number composed of the magnitude of `self` and the sign of + /// `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is + /// returned. + /// + /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note + /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust + /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the + /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable + /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more + /// info. + /// + /// # Examples + /// + /// ``` + /// let f = 3.5_f64; + /// + /// assert_eq!(f.copysign(0.42), 3.5_f64); + /// assert_eq!(f.copysign(-0.42), -3.5_f64); + /// assert_eq!((-f).copysign(0.42), 3.5_f64); + /// assert_eq!((-f).copysign(-0.42), -3.5_f64); + /// + /// assert!(f64::NAN.copysign(1.0).is_nan()); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[stable(feature = "copysign", since = "1.35.0")] + #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] + #[inline] + pub const fn copysign(self, sign: f64) -> f64 { + unsafe { intrinsics::copysignf64(self, sign) } + } } diff --git a/library/std/src/f128.rs b/library/std/src/f128.rs index 229f979b5b10..e93e915159e4 100644 --- a/library/std/src/f128.rs +++ b/library/std/src/f128.rs @@ -188,104 +188,6 @@ impl f128 { self - self.trunc() } - /// Computes the absolute value of `self`. - /// - /// This function always returns the precise result. - /// - /// # Examples - /// - /// ``` - /// #![feature(f128)] - /// # #[cfg(reliable_f128)] { - /// - /// let x = 3.5_f128; - /// let y = -3.5_f128; - /// - /// assert_eq!(x.abs(), x); - /// assert_eq!(y.abs(), -y); - /// - /// assert!(f128::NAN.abs().is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f128", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn abs(self) -> Self { - // FIXME(f16_f128): replace with `intrinsics::fabsf128` when available - // We don't do this now because LLVM has lowering bugs for f128 math. - Self::from_bits(self.to_bits() & !(1 << 127)) - } - - /// Returns a number that represents the sign of `self`. - /// - /// - `1.0` if the number is positive, `+0.0` or `INFINITY` - /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` - /// - NaN if the number is NaN - /// - /// # Examples - /// - /// ``` - /// #![feature(f128)] - /// # #[cfg(reliable_f128_math)] { - /// - /// let f = 3.5_f128; - /// - /// assert_eq!(f.signum(), 1.0); - /// assert_eq!(f128::NEG_INFINITY.signum(), -1.0); - /// - /// assert!(f128::NAN.signum().is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f128", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn signum(self) -> f128 { - if self.is_nan() { Self::NAN } else { 1.0_f128.copysign(self) } - } - - /// Returns a number composed of the magnitude of `self` and the sign of - /// `sign`. - /// - /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. - /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is - /// returned. - /// - /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note - /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust - /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the - /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable - /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more - /// info. - /// - /// # Examples - /// - /// ``` - /// #![feature(f128)] - /// # #[cfg(reliable_f128_math)] { - /// - /// let f = 3.5_f128; - /// - /// assert_eq!(f.copysign(0.42), 3.5_f128); - /// assert_eq!(f.copysign(-0.42), -3.5_f128); - /// assert_eq!((-f).copysign(0.42), 3.5_f128); - /// assert_eq!((-f).copysign(-0.42), -3.5_f128); - /// - /// assert!(f128::NAN.copysign(1.0).is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f128", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn copysign(self, sign: f128) -> f128 { - unsafe { intrinsics::copysignf128(self, sign) } - } - /// Fused multiply-add. Computes `(self * a) + b` with only one rounding /// error, yielding a more accurate result than an unfused multiply-add. /// diff --git a/library/std/src/f16.rs b/library/std/src/f16.rs index bed21cda1cd9..5b7fcaa28e06 100644 --- a/library/std/src/f16.rs +++ b/library/std/src/f16.rs @@ -188,103 +188,6 @@ impl f16 { self - self.trunc() } - /// Computes the absolute value of `self`. - /// - /// This function always returns the precise result. - /// - /// # Examples - /// - /// ``` - /// #![feature(f16)] - /// # #[cfg(reliable_f16)] { - /// - /// let x = 3.5_f16; - /// let y = -3.5_f16; - /// - /// assert_eq!(x.abs(), x); - /// assert_eq!(y.abs(), -y); - /// - /// assert!(f16::NAN.abs().is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f16", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn abs(self) -> Self { - // FIXME(f16_f128): replace with `intrinsics::fabsf16` when available - Self::from_bits(self.to_bits() & !(1 << 15)) - } - - /// Returns a number that represents the sign of `self`. - /// - /// - `1.0` if the number is positive, `+0.0` or `INFINITY` - /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` - /// - NaN if the number is NaN - /// - /// # Examples - /// - /// ``` - /// #![feature(f16)] - /// # #[cfg(reliable_f16_math)] { - /// - /// let f = 3.5_f16; - /// - /// assert_eq!(f.signum(), 1.0); - /// assert_eq!(f16::NEG_INFINITY.signum(), -1.0); - /// - /// assert!(f16::NAN.signum().is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f16", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn signum(self) -> f16 { - if self.is_nan() { Self::NAN } else { 1.0_f16.copysign(self) } - } - - /// Returns a number composed of the magnitude of `self` and the sign of - /// `sign`. - /// - /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. - /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is - /// returned. - /// - /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note - /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust - /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the - /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable - /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more - /// info. - /// - /// # Examples - /// - /// ``` - /// #![feature(f16)] - /// # #[cfg(reliable_f16_math)] { - /// - /// let f = 3.5_f16; - /// - /// assert_eq!(f.copysign(0.42), 3.5_f16); - /// assert_eq!(f.copysign(-0.42), -3.5_f16); - /// assert_eq!((-f).copysign(0.42), 3.5_f16); - /// assert_eq!((-f).copysign(-0.42), -3.5_f16); - /// - /// assert!(f16::NAN.copysign(1.0).is_nan()); - /// # } - /// ``` - #[inline] - #[rustc_allow_incoherent_impl] - #[unstable(feature = "f16", issue = "116909")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[must_use = "method returns a new number and does not mutate the original value"] - pub const fn copysign(self, sign: f16) -> f16 { - unsafe { intrinsics::copysignf16(self, sign) } - } - /// Fused multiply-add. Computes `(self * a) + b` with only one rounding /// error, yielding a more accurate result than an unfused multiply-add. /// diff --git a/library/std/src/f32.rs b/library/std/src/f32.rs index 30cf4e1f756e..7cb285bbff5f 100644 --- a/library/std/src/f32.rs +++ b/library/std/src/f32.rs @@ -176,90 +176,6 @@ impl f32 { self - self.trunc() } - /// Computes the absolute value of `self`. - /// - /// This function always returns the precise result. - /// - /// # Examples - /// - /// ``` - /// let x = 3.5_f32; - /// let y = -3.5_f32; - /// - /// assert_eq!(x.abs(), x); - /// assert_eq!(y.abs(), -y); - /// - /// assert!(f32::NAN.abs().is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[inline] - pub const fn abs(self) -> f32 { - unsafe { intrinsics::fabsf32(self) } - } - - /// Returns a number that represents the sign of `self`. - /// - /// - `1.0` if the number is positive, `+0.0` or `INFINITY` - /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` - /// - NaN if the number is NaN - /// - /// # Examples - /// - /// ``` - /// let f = 3.5_f32; - /// - /// assert_eq!(f.signum(), 1.0); - /// assert_eq!(f32::NEG_INFINITY.signum(), -1.0); - /// - /// assert!(f32::NAN.signum().is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[inline] - pub const fn signum(self) -> f32 { - if self.is_nan() { Self::NAN } else { 1.0_f32.copysign(self) } - } - - /// Returns a number composed of the magnitude of `self` and the sign of - /// `sign`. - /// - /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. - /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is - /// returned. - /// - /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note - /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust - /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the - /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable - /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more - /// info. - /// - /// # Examples - /// - /// ``` - /// let f = 3.5_f32; - /// - /// assert_eq!(f.copysign(0.42), 3.5_f32); - /// assert_eq!(f.copysign(-0.42), -3.5_f32); - /// assert_eq!((-f).copysign(0.42), 3.5_f32); - /// assert_eq!((-f).copysign(-0.42), -3.5_f32); - /// - /// assert!(f32::NAN.copysign(1.0).is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[inline] - #[stable(feature = "copysign", since = "1.35.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - pub const fn copysign(self, sign: f32) -> f32 { - unsafe { intrinsics::copysignf32(self, sign) } - } - /// Fused multiply-add. Computes `(self * a) + b` with only one rounding /// error, yielding a more accurate result than an unfused multiply-add. /// diff --git a/library/std/src/f64.rs b/library/std/src/f64.rs index 51d5476b372d..47163c272de3 100644 --- a/library/std/src/f64.rs +++ b/library/std/src/f64.rs @@ -176,90 +176,6 @@ impl f64 { self - self.trunc() } - /// Computes the absolute value of `self`. - /// - /// This function always returns the precise result. - /// - /// # Examples - /// - /// ``` - /// let x = 3.5_f64; - /// let y = -3.5_f64; - /// - /// assert_eq!(x.abs(), x); - /// assert_eq!(y.abs(), -y); - /// - /// assert!(f64::NAN.abs().is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[inline] - pub const fn abs(self) -> f64 { - unsafe { intrinsics::fabsf64(self) } - } - - /// Returns a number that represents the sign of `self`. - /// - /// - `1.0` if the number is positive, `+0.0` or `INFINITY` - /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` - /// - NaN if the number is NaN - /// - /// # Examples - /// - /// ``` - /// let f = 3.5_f64; - /// - /// assert_eq!(f.signum(), 1.0); - /// assert_eq!(f64::NEG_INFINITY.signum(), -1.0); - /// - /// assert!(f64::NAN.signum().is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[inline] - pub const fn signum(self) -> f64 { - if self.is_nan() { Self::NAN } else { 1.0_f64.copysign(self) } - } - - /// Returns a number composed of the magnitude of `self` and the sign of - /// `sign`. - /// - /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. - /// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is - /// returned. - /// - /// If `sign` is a NaN, then this operation will still carry over its sign into the result. Note - /// that IEEE 754 doesn't assign any meaning to the sign bit in case of a NaN, and as Rust - /// doesn't guarantee that the bit pattern of NaNs are conserved over arithmetic operations, the - /// result of `copysign` with `sign` being a NaN might produce an unexpected or non-portable - /// result. See the [specification of NaN bit patterns](primitive@f32#nan-bit-patterns) for more - /// info. - /// - /// # Examples - /// - /// ``` - /// let f = 3.5_f64; - /// - /// assert_eq!(f.copysign(0.42), 3.5_f64); - /// assert_eq!(f.copysign(-0.42), -3.5_f64); - /// assert_eq!((-f).copysign(0.42), 3.5_f64); - /// assert_eq!((-f).copysign(-0.42), -3.5_f64); - /// - /// assert!(f64::NAN.copysign(1.0).is_nan()); - /// ``` - #[rustc_allow_incoherent_impl] - #[must_use = "method returns a new number and does not mutate the original value"] - #[stable(feature = "copysign", since = "1.35.0")] - #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] - #[inline] - pub const fn copysign(self, sign: f64) -> f64 { - unsafe { intrinsics::copysignf64(self, sign) } - } - /// Fused multiply-add. Computes `(self * a) + b` with only one rounding /// error, yielding a more accurate result than an unfused multiply-add. /// From f512051c0ecc1abe83a294a1b7ffed48a44b0ca6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 5 Oct 2024 21:26:25 +0200 Subject: [PATCH 020/214] adjust test gating for f16/f128 --- library/core/src/num/f128.rs | 6 +++--- library/core/src/num/f16.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index cbe1a6060de4..a551ccfe9ed9 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1291,7 +1291,7 @@ impl f128 { /// /// ``` /// #![feature(f128)] - /// # #[cfg(reliable_f128)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let x = 3.5_f128; /// let y = -3.5_f128; @@ -1323,7 +1323,7 @@ impl f128 { /// /// ``` /// #![feature(f128)] - /// # #[cfg(reliable_f128_math)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let f = 3.5_f128; /// @@ -1360,7 +1360,7 @@ impl f128 { /// /// ``` /// #![feature(f128)] - /// # #[cfg(reliable_f128_math)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let f = 3.5_f128; /// diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 19263062c757..0d77377af746 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1266,7 +1266,7 @@ impl f16 { /// /// ``` /// #![feature(f16)] - /// # #[cfg(reliable_f16)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let x = 3.5_f16; /// let y = -3.5_f16; @@ -1297,7 +1297,7 @@ impl f16 { /// /// ``` /// #![feature(f16)] - /// # #[cfg(reliable_f16_math)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let f = 3.5_f16; /// @@ -1334,7 +1334,7 @@ impl f16 { /// /// ``` /// #![feature(f16)] - /// # #[cfg(reliable_f16_math)] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let f = 3.5_f16; /// From b0224fb794f2aaa1449f02b873976c95b04eeca0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 5 Oct 2024 21:35:04 +0200 Subject: [PATCH 021/214] add missing safety comments --- library/core/src/num/f128.rs | 1 + library/core/src/num/f16.rs | 1 + library/core/src/num/f32.rs | 2 ++ library/core/src/num/f64.rs | 2 ++ 4 files changed, 6 insertions(+) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index a551ccfe9ed9..90d3035f1573 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1378,6 +1378,7 @@ impl f128 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] pub const fn copysign(self, sign: f128) -> f128 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::copysignf128(self, sign) } } } diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 0d77377af746..85b2ad18886a 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1352,6 +1352,7 @@ impl f16 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] pub const fn copysign(self, sign: f16) -> f16 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::copysignf16(self, sign) } } } diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index d9337af316ce..51241fa10278 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1449,6 +1449,7 @@ impl f32 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[inline] pub const fn abs(self) -> f32 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::fabsf32(self) } } @@ -1509,6 +1510,7 @@ impl f32 { #[stable(feature = "copysign", since = "1.35.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] pub const fn copysign(self, sign: f32) -> f32 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::copysignf32(self, sign) } } } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index bcab5193718d..f42eaf831774 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1449,6 +1449,7 @@ impl f64 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[inline] pub const fn abs(self) -> f64 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::fabsf64(self) } } @@ -1509,6 +1510,7 @@ impl f64 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[inline] pub const fn copysign(self, sign: f64) -> f64 { + // SAFETY: this is actually a safe intrinsic unsafe { intrinsics::copysignf64(self, sign) } } } From 7d7c0541b93df64f703c646a931488df5ce88d45 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 1 Nov 2024 16:50:11 +0100 Subject: [PATCH 022/214] remove no-longer-needed attribute --- library/core/src/num/f128.rs | 3 --- library/core/src/num/f16.rs | 3 --- library/core/src/num/f32.rs | 3 --- library/core/src/num/f64.rs | 3 --- 4 files changed, 12 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 90d3035f1573..a1e6079a37d5 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1303,7 +1303,6 @@ impl f128 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f128", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] @@ -1334,7 +1333,6 @@ impl f128 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f128", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] @@ -1373,7 +1371,6 @@ impl f128 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f128", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 85b2ad18886a..77dc8bddf164 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1278,7 +1278,6 @@ impl f16 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f16", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] @@ -1308,7 +1307,6 @@ impl f16 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f16", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] @@ -1347,7 +1345,6 @@ impl f16 { /// # } /// ``` #[inline] - #[rustc_allow_incoherent_impl] #[unstable(feature = "f16", issue = "116909")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 51241fa10278..953deb037b76 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1443,7 +1443,6 @@ impl f32 { /// /// assert!(f32::NAN.abs().is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] @@ -1469,7 +1468,6 @@ impl f32 { /// /// assert!(f32::NAN.signum().is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] @@ -1504,7 +1502,6 @@ impl f32 { /// /// assert!(f32::NAN.copysign(1.0).is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[inline] #[stable(feature = "copysign", since = "1.35.0")] diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index f42eaf831774..edcf7b9dc963 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1443,7 +1443,6 @@ impl f64 { /// /// assert!(f64::NAN.abs().is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] @@ -1469,7 +1468,6 @@ impl f64 { /// /// assert!(f64::NAN.signum().is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] @@ -1504,7 +1502,6 @@ impl f64 { /// /// assert!(f64::NAN.copysign(1.0).is_nan()); /// ``` - #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "copysign", since = "1.35.0")] #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] From 6205bcf0f15038a74b9c156a541ea29479c50ffd Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Fri, 1 Nov 2024 19:30:37 +0000 Subject: [PATCH 023/214] Add match-based manual try to clippy::question_mark --- clippy_lints/src/question_mark.rs | 170 +++++++++++++++++++++++++++++- tests/ui/question_mark.fixed | 46 ++++++++ tests/ui/question_mark.rs | 61 +++++++++++ tests/ui/question_mark.stderr | 63 +++++++++-- 4 files changed, 327 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 28968866e1f8..ce629717617a 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -8,18 +8,18 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{ eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, - pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, - span_contains_comment, + pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, + span_contains_cfg, span_contains_comment, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::def::Res; use rustc_hir::{ - BindingMode, Block, Body, ByRef, Expr, ExprKind, LetStmt, Mutability, Node, PatKind, PathSegment, QPath, Stmt, - StmtKind, + Arm, BindingMode, Block, Body, ByRef, Expr, ExprKind, FnRetTy, HirId, LetStmt, MatchSource, Mutability, Node, Pat, + PatKind, PathSegment, QPath, Stmt, StmtKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::sym; use rustc_span::symbol::Symbol; @@ -58,6 +58,9 @@ pub struct QuestionMark { /// if it is greater than zero. /// As for why we need this in the first place: try_block_depth_stack: Vec, + /// Keeps track of the number of inferred return type closures we are inside, to avoid problems + /// with the `Err(x.into())` expansion being ambiguious. + inferred_ret_closure_stack: u16, } impl_lint_pass!(QuestionMark => [QUESTION_MARK, MANUAL_LET_ELSE]); @@ -68,6 +71,7 @@ impl QuestionMark { msrv: conf.msrv.clone(), matches_behaviour: conf.matches_for_let_else, try_block_depth_stack: Vec::new(), + inferred_ret_closure_stack: 0, } } } @@ -271,6 +275,135 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex } } +#[derive(Clone, Copy, Debug)] +enum TryMode { + Result, + Option, +} + +fn find_try_mode<'tcx>(cx: &LateContext<'tcx>, scrutinee: &Expr<'tcx>) -> Option { + let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee); + let ty::Adt(scrutinee_adt_def, _) = scrutinee_ty.kind() else { + return None; + }; + + match cx.tcx.get_diagnostic_name(scrutinee_adt_def.did())? { + sym::Result => Some(TryMode::Result), + sym::Option => Some(TryMode::Option), + _ => None, + } +} + +// Check that `pat` is `{ctor_lang_item}(val)`, returning `val`. +fn extract_ctor_call<'a, 'tcx>( + cx: &LateContext<'tcx>, + expected_ctor: LangItem, + pat: &'a Pat<'tcx>, +) -> Option<&'a Pat<'tcx>> { + if let PatKind::TupleStruct(variant_path, [val_binding], _) = &pat.kind + && is_res_lang_ctor(cx, cx.qpath_res(variant_path, pat.hir_id), expected_ctor) + { + Some(val_binding) + } else { + None + } +} + +// Extracts the local ID of a plain `val` pattern. +fn extract_binding_pat(pat: &Pat<'_>) -> Option { + if let PatKind::Binding(BindingMode::NONE, binding, _, None) = pat.kind { + Some(binding) + } else { + None + } +} + +fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { + let happy_ctor = match mode { + TryMode::Result => ResultOk, + TryMode::Option => OptionSome, + }; + + // Check for `Ok(val)` or `Some(val)` + if arm.guard.is_none() + && let Some(val_binding) = extract_ctor_call(cx, happy_ctor, arm.pat) + // Extract out `val` + && let Some(binding) = extract_binding_pat(val_binding) + // Check body is just `=> val` + && path_to_local_id(peel_blocks(arm.body), binding) + { + true + } else { + false + } +} + +fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { + if arm.guard.is_some() { + return false; + } + + let arm_body = peel_blocks(arm.body); + match mode { + TryMode::Result => { + // Check that pat is Err(val) + if let Some(ok_pat) = extract_ctor_call(cx, ResultErr, arm.pat) + && let Some(ok_val) = extract_binding_pat(ok_pat) + // check `=> return Err(...)` + && let ExprKind::Ret(Some(wrapped_ret_expr)) = arm_body.kind + && let ExprKind::Call(ok_ctor, [ret_expr]) = wrapped_ret_expr.kind + && is_res_lang_ctor(cx, path_res(cx, ok_ctor), ResultErr) + // check `...` is `val` from binding + && path_to_local_id(ret_expr, ok_val) + { + true + } else { + false + } + }, + TryMode::Option => { + // Check the pat is `None` + if is_res_lang_ctor(cx, path_res(cx, arm.pat), OptionNone) + // Check `=> return None` + && let ExprKind::Ret(Some(ret_expr)) = arm_body.kind + && is_res_lang_ctor(cx, path_res(cx, ret_expr), OptionNone) + && !ret_expr.span.from_expansion() + { + true + } else { + false + } + }, + } +} + +fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool { + (check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2)) + || (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1)) +} + +fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal | MatchSource::Postfix) = expr.kind + && !expr.span.from_expansion() + && let Some(mode) = find_try_mode(cx, scrutinee) + && !span_contains_cfg(cx, expr.span) + && check_arms_are_try(cx, mode, arm1, arm2) + { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability); + + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this `match` expression can be replaced with `?`", + "try instead", + snippet.into_owned() + "?", + applicability, + ); + } +} + fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if let Some(higher::IfLet { let_pat, @@ -339,6 +472,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool { } } +fn is_inferred_ret_closure(expr: &Expr<'_>) -> bool { + let ExprKind::Closure(closure) = expr.kind else { + return false; + }; + + match closure.fn_decl.output { + FnRetTy::Return(ret_ty) => matches!(ret_ty.kind, TyKind::Infer), + FnRetTy::DefaultReturn(_) => true, + } +} + impl<'tcx> LateLintPass<'tcx> for QuestionMark { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) { @@ -350,11 +494,27 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark { } self.check_manual_let_else(cx, stmt); } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if is_inferred_ret_closure(expr) { + self.inferred_ret_closure_stack += 1; + return; + } + if !self.inside_try_block() && !is_in_const_context(cx) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id) { check_is_none_or_err_and_early_return(cx, expr); check_if_let_some_or_err_and_early_return(cx, expr); + + if self.inferred_ret_closure_stack == 0 { + check_if_try_match(cx, expr); + } + } + } + + fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if is_inferred_ret_closure(expr) { + self.inferred_ret_closure_stack -= 1; } } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 679388372e61..fb5bb953b724 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -96,12 +96,42 @@ impl MoveStruct { } fn func() -> Option { + macro_rules! opt_none { + () => { + None + }; + } + fn f() -> Option { Some(String::new()) } f()?; + let _val = f()?; + + let s: &str = match &Some(String::new()) { + Some(v) => v, + None => return None, + }; + + f()?; + + opt_none!()?; + + match f() { + Some(x) => x, + None => return opt_none!(), + }; + + match f() { + Some(val) => { + println!("{val}"); + val + }, + None => return None, + }; + Some(0) } @@ -114,6 +144,10 @@ fn result_func(x: Result) -> Result { x?; + let _val = func_returning_result()?; + + func_returning_result()?; + // No warning let y = if let Ok(x) = x { x @@ -157,6 +191,18 @@ fn result_func(x: Result) -> Result { Ok(y) } +fn infer_check() { + let closure = |x: Result| { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; +} + // see issue #8019 pub enum NotOption { None, diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 601ab78bf5aa..f266bd9d5adf 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -124,6 +124,12 @@ impl MoveStruct { } fn func() -> Option { + macro_rules! opt_none { + () => { + None + }; + } + fn f() -> Option { Some(String::new()) } @@ -132,6 +138,39 @@ fn func() -> Option { return None; } + let _val = match f() { + Some(val) => val, + None => return None, + }; + + let s: &str = match &Some(String::new()) { + Some(v) => v, + None => return None, + }; + + match f() { + Some(val) => val, + None => return None, + }; + + match opt_none!() { + Some(x) => x, + None => return None, + }; + + match f() { + Some(x) => x, + None => return opt_none!(), + }; + + match f() { + Some(val) => { + println!("{val}"); + val + }, + None => return None, + }; + Some(0) } @@ -146,6 +185,16 @@ fn result_func(x: Result) -> Result { return x; } + let _val = match func_returning_result() { + Ok(val) => val, + Err(err) => return Err(err), + }; + + match func_returning_result() { + Ok(val) => val, + Err(err) => return Err(err), + }; + // No warning let y = if let Ok(x) = x { x @@ -189,6 +238,18 @@ fn result_func(x: Result) -> Result { Ok(y) } +fn infer_check() { + let closure = |x: Result| { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; +} + // see issue #8019 pub enum NotOption { None, diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 5f26a7ea2c3e..8ba5f5906690 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -94,29 +94,76 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:131:5 + --> tests/ui/question_mark.rs:137:5 | LL | / if f().is_none() { LL | | return None; LL | | } | |_____^ help: replace it with: `f()?;` +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:141:16 + | +LL | let _val = match f() { + | ________________^ +LL | | Some(val) => val, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `f()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:151:5 + | +LL | / match f() { +LL | | Some(val) => val, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `f()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:156:5 + | +LL | / match opt_none!() { +LL | | Some(x) => x, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `opt_none!()?` + error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:143:13 + --> tests/ui/question_mark.rs:182:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:145:5 + --> tests/ui/question_mark.rs:184:5 | LL | / if x.is_err() { LL | | return x; LL | | } | |_____^ help: replace it with: `x?;` +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:188:16 + | +LL | let _val = match func_returning_result() { + | ________________^ +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err), +LL | | }; + | |_____^ help: try instead: `func_returning_result()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:193:5 + | +LL | / match func_returning_result() { +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err), +LL | | }; + | |_____^ help: try instead: `func_returning_result()?` + error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:213:5 + --> tests/ui/question_mark.rs:274:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -124,7 +171,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:220:5 + --> tests/ui/question_mark.rs:281:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -132,7 +179,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:297:13 + --> tests/ui/question_mark.rs:358:13 | LL | / if a.is_none() { LL | | return None; @@ -142,12 +189,12 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:357:5 + --> tests/ui/question_mark.rs:418:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; LL | | }; | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;` -error: aborting due to 17 previous errors +error: aborting due to 22 previous errors From 139bb25927f75c53e05d0c36dc9cfbc8081750c5 Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Sat, 2 Nov 2024 13:41:16 +0000 Subject: [PATCH 024/214] Avoid linting for closures with inferred return types --- clippy_lints/src/question_mark.rs | 4 ++-- tests/ui/question_mark.fixed | 10 ++++++++++ tests/ui/question_mark.rs | 10 ++++++++++ tests/ui/question_mark.stderr | 8 ++++---- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index ce629717617a..7d1ba45c3ea6 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -16,7 +16,7 @@ use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::def::Res; use rustc_hir::{ Arm, BindingMode, Block, Body, ByRef, Expr, ExprKind, FnRetTy, HirId, LetStmt, MatchSource, Mutability, Node, Pat, - PatKind, PathSegment, QPath, Stmt, StmtKind, TyKind, + PatKind, PathSegment, QPath, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; @@ -478,7 +478,7 @@ fn is_inferred_ret_closure(expr: &Expr<'_>) -> bool { }; match closure.fn_decl.output { - FnRetTy::Return(ret_ty) => matches!(ret_ty.kind, TyKind::Infer), + FnRetTy::Return(ret_ty) => ret_ty.is_suggestable_infer_ty(), FnRetTy::DefaultReturn(_) => true, } } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index fb5bb953b724..b6e148e9f772 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -201,6 +201,16 @@ fn infer_check() { Ok(()) }; + + let closure = |x: Result| -> Result<(), _> { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; } // see issue #8019 diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index f266bd9d5adf..48dc9eb0a626 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -248,6 +248,16 @@ fn infer_check() { Ok(()) }; + + let closure = |x: Result| -> Result<(), _> { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; } // see issue #8019 diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 8ba5f5906690..0a48c4e80cb6 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -163,7 +163,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:274:5 + --> tests/ui/question_mark.rs:284:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -171,7 +171,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:281:5 + --> tests/ui/question_mark.rs:291:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -179,7 +179,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:358:13 + --> tests/ui/question_mark.rs:368:13 | LL | / if a.is_none() { LL | | return None; @@ -189,7 +189,7 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:418:5 + --> tests/ui/question_mark.rs:428:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; From 33f500d52b0741e3f9d19b16e541c9cf438692ca Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 17 Jan 2024 17:12:30 -0800 Subject: [PATCH 025/214] Add Set entry API Signed-off-by: Alex Saveau --- library/std/src/collections/hash/set.rs | 449 ++++++++++++++++++++++++ 1 file changed, 449 insertions(+) diff --git a/library/std/src/collections/hash/set.rs b/library/std/src/collections/hash/set.rs index e1e0eb36d23f..f86bcdb4796e 100644 --- a/library/std/src/collections/hash/set.rs +++ b/library/std/src/collections/hash/set.rs @@ -757,6 +757,47 @@ where self.base.get_or_insert_with(value, f) } + /// Gets the given value's corresponding entry in the set for in-place manipulation. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// use std::collections::hash_set::Entry::*; + /// + /// let mut singles = HashSet::new(); + /// let mut dupes = HashSet::new(); + /// + /// for ch in "a short treatise on fungi".chars() { + /// if let Vacant(dupe_entry) = dupes.entry(ch) { + /// // We haven't already seen a duplicate, so + /// // check if we've at least seen it once. + /// match singles.entry(ch) { + /// Vacant(single_entry) => { + /// // We found a new character for the first time. + /// single_entry.insert() + /// } + /// Occupied(single_entry) => { + /// // We've already seen this once, "move" it to dupes. + /// single_entry.remove(); + /// dupe_entry.insert(); + /// } + /// } + /// } + /// } + /// + /// assert!(!singles.contains(&'t') && dupes.contains(&'t')); + /// assert!(singles.contains(&'u') && !dupes.contains(&'u')); + /// assert!(!singles.contains(&'v') && !dupes.contains(&'v')); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn entry(&mut self, value: T) -> Entry<'_, T, S> { + map_entry(self.base.entry(value)) + } + /// Returns `true` if `self` has no elements in common with `other`. /// This is equivalent to checking for an empty intersection. /// @@ -935,6 +976,14 @@ where } } +#[inline] +fn map_entry<'a, K: 'a, V: 'a>(raw: base::Entry<'a, K, V>) -> Entry<'a, K, V> { + match raw { + base::Entry::Occupied(base) => Entry::Occupied(OccupiedEntry { base }), + base::Entry::Vacant(base) => Entry::Vacant(VacantEntry { base }), + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Clone for HashSet where @@ -1865,6 +1914,406 @@ where } } +/// A view into a single entry in a set, which may either be vacant or occupied. +/// +/// This `enum` is constructed from the [`entry`] method on [`HashSet`]. +/// +/// [`HashSet`]: struct.HashSet.html +/// [`entry`]: struct.HashSet.html#method.entry +/// +/// # Examples +/// +/// ``` +/// #![feature(hash_set_entry)] +/// +/// use std::collections::hash_set::HashSet; +/// +/// let mut set = HashSet::new(); +/// set.extend(["a", "b", "c"]); +/// assert_eq!(set.len(), 3); +/// +/// // Existing value (insert) +/// let entry = set.entry("a"); +/// let _raw_o = entry.insert(); +/// assert_eq!(set.len(), 3); +/// // Nonexistent value (insert) +/// set.entry("d").insert(); +/// +/// // Existing value (or_insert) +/// set.entry("b").or_insert(); +/// // Nonexistent value (or_insert) +/// set.entry("e").or_insert(); +/// +/// println!("Our HashSet: {:?}", set); +/// +/// let mut vec: Vec<_> = set.iter().copied().collect(); +/// // The `Iter` iterator produces items in arbitrary order, so the +/// // items must be sorted to test them against a sorted array. +/// vec.sort_unstable(); +/// assert_eq!(vec, ["a", "b", "c", "d", "e"]); +/// ``` +#[unstable(feature = "hash_set_entry", issue = "60896")] +pub enum Entry<'a, T, S> { + /// An occupied entry. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::hash_set::{Entry, HashSet}; + /// + /// let mut set = HashSet::from(["a", "b"]); + /// + /// match set.entry("a") { + /// Entry::Vacant(_) => unreachable!(), + /// Entry::Occupied(_) => { } + /// } + /// ``` + Occupied(OccupiedEntry<'a, T, S>), + + /// A vacant entry. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::hash_set::{Entry, HashSet}; + /// + /// let mut set = HashSet::new(); + /// + /// match set.entry("a") { + /// Entry::Occupied(_) => unreachable!(), + /// Entry::Vacant(_) => { } + /// } + /// ``` + Vacant(VacantEntry<'a, T, S>), +} + +#[unstable(feature = "hash_set_entry", issue = "60896")] +impl fmt::Debug for Entry<'_, T, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Entry::Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(), + Entry::Occupied(ref o) => f.debug_tuple("Entry").field(o).finish(), + } + } +} + +/// A view into an occupied entry in a `HashSet`. +/// It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html +/// +/// # Examples +/// +/// ``` +/// #![feature(hash_set_entry)] +/// +/// use std::collections::hash_set::{Entry, HashSet}; +/// +/// let mut set = HashSet::new(); +/// set.extend(["a", "b", "c"]); +/// +/// let _entry_o = set.entry("a").insert(); +/// assert_eq!(set.len(), 3); +/// +/// // Existing key +/// match set.entry("a") { +/// Entry::Vacant(_) => unreachable!(), +/// Entry::Occupied(view) => { +/// assert_eq!(view.get(), &"a"); +/// } +/// } +/// +/// assert_eq!(set.len(), 3); +/// +/// // Existing key (take) +/// match set.entry("c") { +/// Entry::Vacant(_) => unreachable!(), +/// Entry::Occupied(view) => { +/// assert_eq!(view.remove(), "c"); +/// } +/// } +/// assert_eq!(set.get(&"c"), None); +/// assert_eq!(set.len(), 2); +/// ``` +#[unstable(feature = "hash_set_entry", issue = "60896")] +pub struct OccupiedEntry<'a, T, S> { + base: base::OccupiedEntry<'a, T, S>, +} + +#[unstable(feature = "hash_set_entry", issue = "60896")] +impl fmt::Debug for OccupiedEntry<'_, T, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedEntry").field("value", self.get()).finish() + } +} + +/// A view into a vacant entry in a `HashSet`. +/// It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html +/// +/// # Examples +/// +/// ``` +/// #![feature(hash_set_entry)] +/// +/// use std::collections::hash_set::{Entry, HashSet}; +/// +/// let mut set = HashSet::<&str>::new(); +/// +/// let entry_v = match set.entry("a") { +/// Entry::Vacant(view) => view, +/// Entry::Occupied(_) => unreachable!(), +/// }; +/// entry_v.insert(); +/// assert!(set.contains("a") && set.len() == 1); +/// +/// // Nonexistent key (insert) +/// match set.entry("b") { +/// Entry::Vacant(view) => view.insert(), +/// Entry::Occupied(_) => unreachable!(), +/// } +/// assert!(set.contains("b") && set.len() == 2); +/// ``` +#[unstable(feature = "hash_set_entry", issue = "60896")] +pub struct VacantEntry<'a, T, S> { + base: base::VacantEntry<'a, T, S>, +} + +#[unstable(feature = "hash_set_entry", issue = "60896")] +impl fmt::Debug for VacantEntry<'_, T, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VacantEntry").field(self.get()).finish() + } +} + +impl<'a, T, S> Entry<'a, T, S> { + /// Sets the value of the entry, and returns an OccupiedEntry. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// + /// let mut set = HashSet::new(); + /// let entry = set.entry("horseyland").insert(); + /// + /// assert_eq!(entry.get(), &"horseyland"); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn insert(self) -> OccupiedEntry<'a, T, S> + where + T: Hash, + S: BuildHasher, + { + match self { + Entry::Occupied(entry) => entry, + Entry::Vacant(entry) => entry.insert_entry(), + } + } + + /// Ensures a value is in the entry by inserting if it was vacant. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// + /// let mut set = HashSet::new(); + /// + /// // nonexistent key + /// set.entry("poneyland").or_insert(); + /// assert!(set.contains("poneyland")); + /// + /// // existing key + /// set.entry("poneyland").or_insert(); + /// assert!(set.contains("poneyland")); + /// assert_eq!(set.len(), 1); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn or_insert(self) + where + T: Hash, + S: BuildHasher, + { + if let Entry::Vacant(entry) = self { + entry.insert(); + } + } + + /// Returns a reference to this entry's value. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// + /// let mut set = HashSet::new(); + /// set.entry("poneyland").or_insert(); + /// + /// // existing key + /// assert_eq!(set.entry("poneyland").get(), &"poneyland"); + /// // nonexistent key + /// assert_eq!(set.entry("horseland").get(), &"horseland"); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn get(&self) -> &T { + match *self { + Entry::Occupied(ref entry) => entry.get(), + Entry::Vacant(ref entry) => entry.get(), + } + } +} + +impl OccupiedEntry<'_, T, S> { + /// Gets a reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::hash_set::{Entry, HashSet}; + /// + /// let mut set = HashSet::new(); + /// set.entry("poneyland").or_insert(); + /// + /// match set.entry("poneyland") { + /// Entry::Vacant(_) => panic!(), + /// Entry::Occupied(entry) => assert_eq!(entry.get(), &"poneyland"), + /// } + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn get(&self) -> &T { + self.base.get() + } + + /// Takes the value out of the entry, and returns it. + /// Keeps the allocated memory for reuse. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// use std::collections::hash_set::Entry; + /// + /// let mut set = HashSet::new(); + /// // The set is empty + /// assert!(set.is_empty() && set.capacity() == 0); + /// + /// set.entry("poneyland").or_insert(); + /// let capacity_before_remove = set.capacity(); + /// + /// if let Entry::Occupied(o) = set.entry("poneyland") { + /// assert_eq!(o.remove(), "poneyland"); + /// } + /// + /// assert_eq!(set.contains("poneyland"), false); + /// // Now set hold none elements but capacity is equal to the old one + /// assert!(set.len() == 0 && set.capacity() == capacity_before_remove); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn remove(self) -> T { + self.base.remove() + } +} + +impl<'a, T, S> VacantEntry<'a, T, S> { + /// Gets a reference to the value that would be used when inserting + /// through the `VacantEntry`. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// + /// let mut set = HashSet::new(); + /// assert_eq!(set.entry("poneyland").get(), &"poneyland"); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn get(&self) -> &T { + self.base.get() + } + + /// Take ownership of the value. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::hash_set::{Entry, HashSet}; + /// + /// let mut set = HashSet::new(); + /// + /// match set.entry("poneyland") { + /// Entry::Occupied(_) => panic!(), + /// Entry::Vacant(v) => assert_eq!(v.into_value(), "poneyland"), + /// } + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn into_value(self) -> T { + self.base.into_value() + } + + /// Sets the value of the entry with the VacantEntry's value. + /// + /// # Examples + /// + /// ``` + /// #![feature(hash_set_entry)] + /// + /// use std::collections::HashSet; + /// use std::collections::hash_set::Entry; + /// + /// let mut set = HashSet::new(); + /// + /// if let Entry::Vacant(o) = set.entry("poneyland") { + /// o.insert(); + /// } + /// assert!(set.contains("poneyland")); + /// ``` + #[inline] + #[unstable(feature = "hash_set_entry", issue = "60896")] + pub fn insert(self) + where + T: Hash, + S: BuildHasher, + { + self.base.insert(); + } + + #[inline] + fn insert_entry(self) -> OccupiedEntry<'a, T, S> + where + T: Hash, + S: BuildHasher, + { + OccupiedEntry { base: self.base.insert() } + } +} + #[allow(dead_code)] fn assert_covariance() { fn set<'new>(v: HashSet<&'static str>) -> HashSet<&'new str> { From a0b7681a6f5e0522a4e8255961523370256bfd7d Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Sat, 2 Nov 2024 19:47:11 -0700 Subject: [PATCH 026/214] fix: `identity_op` suggestions use correct parenthesis The `identity_op` lint was suggesting code fixes that resulted in incorrect or broken code, due to missing parenthesis in the fix that changed the semantics of the code. For a binary expression, `left op right`, if the `left` was redundant, it would check if the right side needed parenthesis, but if the `right` was redundant, it would just assume that the left side did not need parenthesis. This can result in either rustfix generating broken code and failing, or code that has different behavior than before the fix. e.g. `-(x + y + 0)` would turn into `-x + y`, changing the behavior, and `1u64 + (x + y + 0i32) as u64` where `x: i32` and `y: i32` would turn into `1u64 + x + y as u64`, creating broken code where `x` cannot be added to the other values, as it was never cast to `u64`. This commit fixes both of these cases by always checking the non-redundant child of a binary expression for needed parenthesis, and makes it so if we need parenthesis, but they already exist, we don't add any redundant ones. Fixes #13470 --- clippy_lints/src/operators/identity_op.rs | 213 +++++++++++----------- tests/ui/identity_op.fixed | 42 ++++- tests/ui/identity_op.rs | 40 ++++ tests/ui/identity_op.stderr | 64 ++++++- 4 files changed, 253 insertions(+), 106 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 830be50c8ba4..d8430f6492d2 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -42,83 +42,53 @@ pub(crate) fn check<'tcx>( match op { BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - let _ = check_op( - cx, - left, - 0, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - 0, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, 0) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, 0) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - let _ = check_op( - cx, - right, - 0, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, right, 0) { + span_ineffective_operation( + cx, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + } }, BinOpKind::Mul => { - let _ = check_op( - cx, - left, - 1, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - 1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, 1) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, 1) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Div => { - let _ = check_op( - cx, - right, - 1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, right, 1) { + span_ineffective_operation( + cx, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + } }, BinOpKind::BitAnd => { - let _ = check_op( - cx, - left, - -1, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - -1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, -1) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, -1) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), _ => (), @@ -138,43 +108,75 @@ enum Parens { Unneeded, } -/// Checks if `left op right` needs parenthesis when reduced to `right` +/// Checks if a binary expression needs parenthesis when reduced to just its +/// right or left child. +/// +/// e.g. `-(x + y + 0)` cannot be reduced to `-x + y`, as the behavior changes silently. +/// e.g. `1u64 + ((x + y + 0i32) as u64)` cannot be reduced to `1u64 + x + y as u64`, since +/// the the cast expression will not apply to the same expression. /// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced /// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be -/// interpreted as a statement +/// interpreted as a statement. The same behavior happens for `match`, `loop`, +/// and blocks. +/// e.g. `2 * (0 + { a })` can be reduced to `2 * { a }` without the need for parenthesis, +/// but `1 * ({ a } + 4)` cannot be reduced to `{ a } + 4`, as a block at the start of a line +/// will be interpreted as a statement instead of an expression. /// -/// See #8724 -fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> Parens { - match right.kind { +/// See #8724, #13470 +fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) -> Parens { + match child.kind { ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => { - // ensure we're checking against the leftmost expression of `right` - // - // ~~~ `lhs` - // 0 + {4} * 2 - // ~~~~~~~ `right` - return needs_parenthesis(cx, binary, lhs); + // For casts and binary expressions, we want to add parenthesis if + // the parent HIR node is an expression with a different precedence, + // or if the parent HIR node is a Block or Stmt, and the new left hand side + // would be treated as a statement rather than an expression. + if let Some((_, parent)) = cx.tcx.hir().parent_iter(binary.hir_id).next() { + if let Node::Expr(expr) = parent { + if child.precedence().order() != expr.precedence().order() { + return Parens::Needed; + } + return Parens::Unneeded; + } + match parent { + Node::Block(_) | Node::Stmt(_) => { + // ensure we're checking against the leftmost expression of `child` + // + // ~~~~~~~~~~~ `binary` + // ~~~ `lhs` + // 0 + {4} * 2 + // ~~~~~~~ `child` + return needs_parenthesis(cx, binary, lhs); + }, + _ => return Parens::Unneeded, + } + } + }, + ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => { + // For if, match, block, and loop expressions, we want to add parenthesis if + // the closest ancestor node that is not an expression is a block or statement. + // This would mean that the rustfix suggestion will appear at the start of a line, which causes + // these expressions to be interpreted as statements if they do not have parenthesis. + let mut prev_id = binary.hir_id; + for (_, parent) in cx.tcx.hir().parent_iter(binary.hir_id) { + if let Node::Expr(expr) = parent + && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) | ExprKind::Unary(_, lhs) = expr.kind + && lhs.hir_id == prev_id + { + // keep going until we find a node that encompasses left of `binary` + prev_id = expr.hir_id; + continue; + } + + match parent { + Node::Block(_) | Node::Stmt(_) => return Parens::Needed, + _ => return Parens::Unneeded, + }; + } + }, + _ => { + return Parens::Unneeded; }, - ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {}, - _ => return Parens::Unneeded, } - - let mut prev_id = binary.hir_id; - for (_, node) in cx.tcx.hir().parent_iter(binary.hir_id) { - if let Node::Expr(expr) = node - && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) = expr.kind - && lhs.hir_id == prev_id - { - // keep going until we find a node that encompasses left of `binary` - prev_id = expr.hir_id; - continue; - } - - match node { - Node::Block(_) | Node::Stmt(_) => break, - _ => return Parens::Unneeded, - }; - } - Parens::Needed } @@ -199,7 +201,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span } } -fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens, is_erased: bool) -> bool { +fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool { if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), @@ -212,7 +214,6 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa 1 => v == 1, _ => unreachable!(), } { - span_ineffective_operation(cx, span, arg, parens, is_erased); return true; } } @@ -234,7 +235,13 @@ fn span_ineffective_operation( expr_snippet.into_owned() }; let suggestion = match parens { - Parens::Needed => format!("({expr_snippet})"), + Parens::Needed => { + if !expr_snippet.starts_with('(') && !expr_snippet.ends_with(')') { + format!("({expr_snippet})") + } else { + expr_snippet + } + }, Parens::Unneeded => expr_snippet, }; diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index b18d8560f6f8..18b7401768df 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -149,7 +149,7 @@ fn main() { 2 * { a }; //~^ ERROR: this operation has no effect - (({ a } + 4)); + ({ a } + 4); //~^ ERROR: this operation has no effect 1; //~^ ERROR: this operation has no effect @@ -212,3 +212,43 @@ fn issue_12050() { //~^ ERROR: this operation has no effect } } + +fn issue_13470() { + let x = 1i32; + let y = 1i32; + // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works + let _: u64 = (x + y) as u64; + //~^ ERROR: this operation has no effect + // both of the next two lines should look the same after rustfix + let _: u64 = 1u64 & (x + y) as u64; + //~^ ERROR: this operation has no effect + // Same as above, but with extra redundant parenthesis + let _: u64 = 1u64 & (x + y) as u64; + //~^ ERROR: this operation has no effect + // Should maintain parenthesis even if the surrounding expr has the same precedence + let _: u64 = 5u64 + (x + y) as u64; + //~^ ERROR: this operation has no effect + + // If we don't maintain the parens here, the behavior changes + let _ = -(x + y); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of higher precedence + let _ = 2i32 * (x + y); + //~^ ERROR: this operation has no effect + // No need for parenthesis if the parent expr is of equal precedence + let _ = 2i32 + x + y; + //~^ ERROR: this operation has no effect + // But make sure that inner parens still exist + let z = 1i32; + let _ = 2 + x + (y * z); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of lower precedence + // This is for clarity, and clippy will not warn on these being unnecessary + let _ = 2i32 + (x * y); + //~^ ERROR: this operation has no effect + + let x = 1i16; + let y = 1i16; + let _: u64 = 1u64 + (x as i32 + y as i32) as u64; + //~^ ERROR: this operation has no effect +} diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index f1f01b424478..55483f4e5fb3 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -212,3 +212,43 @@ fn issue_12050() { //~^ ERROR: this operation has no effect } } + +fn issue_13470() { + let x = 1i32; + let y = 1i32; + // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works + let _: u64 = (x + y + 0i32) as u64; + //~^ ERROR: this operation has no effect + // both of the next two lines should look the same after rustfix + let _: u64 = 1u64 & (x + y + 0i32) as u64; + //~^ ERROR: this operation has no effect + // Same as above, but with extra redundant parenthesis + let _: u64 = 1u64 & ((x + y) + 0i32) as u64; + //~^ ERROR: this operation has no effect + // Should maintain parenthesis even if the surrounding expr has the same precedence + let _: u64 = 5u64 + ((x + y) + 0i32) as u64; + //~^ ERROR: this operation has no effect + + // If we don't maintain the parens here, the behavior changes + let _ = -(x + y + 0i32); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of higher precedence + let _ = 2i32 * (x + y + 0i32); + //~^ ERROR: this operation has no effect + // No need for parenthesis if the parent expr is of equal precedence + let _ = 2i32 + (x + y + 0i32); + //~^ ERROR: this operation has no effect + // But make sure that inner parens still exist + let z = 1i32; + let _ = 2 + (x + (y * z) + 0); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of lower precedence + // This is for clarity, and clippy will not warn on these being unnecessary + let _ = 2i32 + (x * y * 1i32); + //~^ ERROR: this operation has no effect + + let x = 1i16; + let y = 1i16; + let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); + //~^ ERROR: this operation has no effect +} diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 9fff86b86f9f..856bebfa1ffc 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -221,7 +221,7 @@ error: this operation has no effect --> tests/ui/identity_op.rs:152:5 | LL | 1 * ({ a } + 4); - | ^^^^^^^^^^^^^^^ help: consider reducing it to: `(({ a } + 4))` + | ^^^^^^^^^^^^^^^ help: consider reducing it to: `({ a } + 4)` error: this operation has no effect --> tests/ui/identity_op.rs:154:5 @@ -313,5 +313,65 @@ error: this operation has no effect LL | let _: i32 = **&&*&x + 0; | ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x` -error: aborting due to 52 previous errors +error: this operation has no effect + --> tests/ui/identity_op.rs:220:18 + | +LL | let _: u64 = (x + y + 0i32) as u64; + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:223:25 + | +LL | let _: u64 = 1u64 & (x + y + 0i32) as u64; + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:226:25 + | +LL | let _: u64 = 1u64 & ((x + y) + 0i32) as u64; + | ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:229:25 + | +LL | let _: u64 = 5u64 + ((x + y) + 0i32) as u64; + | ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:233:14 + | +LL | let _ = -(x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:236:20 + | +LL | let _ = 2i32 * (x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:239:20 + | +LL | let _ = 2i32 + (x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `x + y` + +error: this operation has no effect + --> tests/ui/identity_op.rs:243:17 + | +LL | let _ = 2 + (x + (y * z) + 0); + | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:247:20 + | +LL | let _ = 2i32 + (x * y * 1i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:252:25 + | +LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` + +error: aborting due to 62 previous errors From 6d738f6bc032ca539d08a7d59674f1e74e539674 Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Sun, 3 Nov 2024 09:23:03 -0800 Subject: [PATCH 027/214] Fix parens getting removed for non-associative operators --- clippy_lints/src/operators/identity_op.rs | 22 ++++++---------------- tests/ui/identity_op.fixed | 5 +++-- tests/ui/identity_op.rs | 5 +++-- tests/ui/identity_op.stderr | 12 ++++++------ 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index d8430f6492d2..e3ce1ae9427d 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -52,13 +52,8 @@ pub(crate) fn check<'tcx>( }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { if is_redundant_op(cx, right, 0) { - span_ineffective_operation( - cx, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Mul => { @@ -127,17 +122,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) match child.kind { ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => { // For casts and binary expressions, we want to add parenthesis if - // the parent HIR node is an expression with a different precedence, - // or if the parent HIR node is a Block or Stmt, and the new left hand side - // would be treated as a statement rather than an expression. + // the parent HIR node is an expression, or if the parent HIR node + // is a Block or Stmt, and the new left hand side would need + // parenthesis be treated as a statement rather than an expression. if let Some((_, parent)) = cx.tcx.hir().parent_iter(binary.hir_id).next() { - if let Node::Expr(expr) = parent { - if child.precedence().order() != expr.precedence().order() { - return Parens::Needed; - } - return Parens::Unneeded; - } match parent { + Node::Expr(_) => return Parens::Needed, Node::Block(_) | Node::Stmt(_) => { // ensure we're checking against the leftmost expression of `child` // diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 18b7401768df..927142d5f802 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -235,8 +235,9 @@ fn issue_13470() { // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y); //~^ ERROR: this operation has no effect - // No need for parenthesis if the parent expr is of equal precedence - let _ = 2i32 + x + y; + // Maintain parenthesis if the parent expr is the same precedence + // as not all operations are associative + let _ = 2i32 - (x - y); //~^ ERROR: this operation has no effect // But make sure that inner parens still exist let z = 1i32; diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index 55483f4e5fb3..a50546929a82 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -235,8 +235,9 @@ fn issue_13470() { // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y + 0i32); //~^ ERROR: this operation has no effect - // No need for parenthesis if the parent expr is of equal precedence - let _ = 2i32 + (x + y + 0i32); + // Maintain parenthesis if the parent expr is the same precedence + // as not all operations are associative + let _ = 2i32 - (x - y - 0i32); //~^ ERROR: this operation has no effect // But make sure that inner parens still exist let z = 1i32; diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 856bebfa1ffc..79d1a7bd81c3 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -350,25 +350,25 @@ LL | let _ = 2i32 * (x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:239:20 + --> tests/ui/identity_op.rs:240:20 | -LL | let _ = 2i32 + (x + y + 0i32); - | ^^^^^^^^^^^^^^ help: consider reducing it to: `x + y` +LL | let _ = 2i32 - (x - y - 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x - y)` error: this operation has no effect - --> tests/ui/identity_op.rs:243:17 + --> tests/ui/identity_op.rs:244:17 | LL | let _ = 2 + (x + (y * z) + 0); | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` error: this operation has no effect - --> tests/ui/identity_op.rs:247:20 + --> tests/ui/identity_op.rs:248:20 | LL | let _ = 2i32 + (x * y * 1i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` error: this operation has no effect - --> tests/ui/identity_op.rs:252:25 + --> tests/ui/identity_op.rs:253:25 | LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` From 8071cbd73021a4e1b9edcbe8b4f47cca3f0cdf9e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 4 Nov 2024 07:58:26 +0100 Subject: [PATCH 028/214] Render extern blocks in file_structure --- .../crates/ide/src/file_structure.rs | 59 ++++++++++++++----- .../crates/rust-analyzer/src/lsp/to_proto.rs | 1 + 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs index 055080ad17b1..5ef65c209cab 100644 --- a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs +++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs @@ -19,6 +19,7 @@ pub struct StructureNode { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum StructureNodeKind { SymbolKind(SymbolKind), + ExternBlock, Region, } @@ -158,6 +159,7 @@ fn structure_node(node: &SyntaxNode) -> Option { ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)), ast::TraitAlias(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::TraitAlias)), ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)), + ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)), ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)), ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)), ast::Const(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Const)), @@ -205,7 +207,23 @@ fn structure_node(node: &SyntaxNode) -> Option { Some(node) }, - ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)), + ast::ExternBlock(it) => { + let mut label = "extern".to_owned(); + let abi = it.abi()?; + if let Some(abi) = abi.string_token() { + label.push(' '); + label.push_str(abi.text()); + } + Some(StructureNode { + parent: None, + label, + navigation_range: abi.syntax().text_range(), + node_range: it.syntax().text_range(), + kind: StructureNodeKind::ExternBlock, + detail: None, + deprecated: false, + }) + }, _ => None, } } @@ -327,6 +345,8 @@ fn f() {} fn g() {} } +extern "C" {} + fn let_statements() { let x = 42; let mut y = x; @@ -662,11 +682,20 @@ fn let_statements() { ), deprecated: false, }, + StructureNode { + parent: None, + label: "extern \"C\"", + navigation_range: 638..648, + node_range: 638..651, + kind: ExternBlock, + detail: None, + deprecated: false, + }, StructureNode { parent: None, label: "let_statements", - navigation_range: 641..655, - node_range: 638..798, + navigation_range: 656..670, + node_range: 653..813, kind: SymbolKind( Function, ), @@ -677,11 +706,11 @@ fn let_statements() { }, StructureNode { parent: Some( - 26, + 27, ), label: "x", - navigation_range: 668..669, - node_range: 664..675, + navigation_range: 683..684, + node_range: 679..690, kind: SymbolKind( Local, ), @@ -690,11 +719,11 @@ fn let_statements() { }, StructureNode { parent: Some( - 26, + 27, ), label: "mut y", - navigation_range: 684..689, - node_range: 680..694, + navigation_range: 699..704, + node_range: 695..709, kind: SymbolKind( Local, ), @@ -703,11 +732,11 @@ fn let_statements() { }, StructureNode { parent: Some( - 26, + 27, ), label: "Foo { .. }", - navigation_range: 703..725, - node_range: 699..738, + navigation_range: 718..740, + node_range: 714..753, kind: SymbolKind( Local, ), @@ -716,11 +745,11 @@ fn let_statements() { }, StructureNode { parent: Some( - 26, + 27, ), label: "_", - navigation_range: 788..789, - node_range: 784..796, + navigation_range: 803..804, + node_range: 799..811, kind: SymbolKind( Local, ), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index d654dc3e7f4a..c59e4699f27f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -88,6 +88,7 @@ pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolK match kind { StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol), StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE, + StructureNodeKind::ExternBlock => lsp_types::SymbolKind::NAMESPACE, } } From 5cbfb7422b09d46734f2a4a150fb581ede92d735 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 3 Nov 2024 16:29:35 +0100 Subject: [PATCH 029/214] Support new #[rustc_intrinsic] attribute and fallback bodies --- .../rust-analyzer/crates/hir-def/src/data.rs | 4 - .../crates/hir-expand/src/inert_attr_macro.rs | 13 ++ .../hir-ty/src/consteval/tests/intrinsics.rs | 97 ++++++------ .../hir-ty/src/diagnostics/decl_check.rs | 2 +- .../crates/hir-ty/src/mir/eval/shim.rs | 144 +++++++----------- .../rust-analyzer/crates/hir-ty/src/utils.rs | 22 +-- src/tools/rust-analyzer/crates/hir/src/lib.rs | 19 +-- .../crates/intern/src/symbol/symbols.rs | 6 +- .../crates/test-utils/src/minicore.rs | 10 +- 9 files changed, 143 insertions(+), 174 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index 6d07dc8f9beb..2a13f74aac71 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -13,7 +13,6 @@ use syntax::{ast, Parse}; use triomphe::Arc; use crate::{ - attr::Attrs, db::DefDatabase, expander::{Expander, Mark}, item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, TreeId}, @@ -37,8 +36,6 @@ pub struct FunctionData { pub name: Name, pub params: Box<[TypeRefId]>, pub ret_type: TypeRefId, - // FIXME: why are these stored here? They should be accessed via the query - pub attrs: Attrs, pub visibility: RawVisibility, pub abi: Option, pub legacy_const_generics_indices: Option>>, @@ -115,7 +112,6 @@ impl FunctionData { .filter_map(|(_, param)| param.type_ref) .collect(), ret_type: func.ret_type, - attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()), visibility, abi: func.abi.clone(), legacy_const_generics_indices, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/inert_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/inert_attr_macro.rs index 5c25a55362e6..95dfe56ff544 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/inert_attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/inert_attr_macro.rs @@ -632,6 +632,19 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_safe_intrinsic, Normal, template!(Word), WarnFollowing, "the `#[rustc_safe_intrinsic]` attribute is used internally to mark intrinsics as safe" ), + rustc_attr!( + rustc_intrinsic, Normal, template!(Word), ErrorFollowing, + "the `#[rustc_intrinsic]` attribute is used to declare intrinsics with function bodies", + ), + rustc_attr!( + rustc_no_mir_inline, Normal, template!(Word), WarnFollowing, + "#[rustc_no_mir_inline] prevents the MIR inliner from inlining a function while not affecting codegen" + ), + rustc_attr!( + rustc_intrinsic_must_be_overridden, Normal, template!(Word), ErrorFollowing, + "the `#[rustc_intrinsic_must_be_overridden]` attribute is used to declare intrinsics without real bodies", + ), + rustc_attr!( rustc_deprecated_safe_2024, Normal, template!(Word), WarnFollowing, "the `#[rustc_safe_intrinsic]` marks functions as unsafe in Rust 2024", diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs index c5706172b20c..c1ac7ae173b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -4,9 +4,8 @@ use super::*; fn size_of() { check_number( r#" - extern "rust-intrinsic" { - pub fn size_of() -> usize; - } + #[rustc_intrinsic] + pub fn size_of() -> usize; const GOAL: usize = size_of::(); "#, @@ -19,9 +18,8 @@ fn size_of_val() { check_number( r#" //- minicore: coerce_unsized - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; struct X(i32, u8); @@ -32,9 +30,8 @@ fn size_of_val() { check_number( r#" //- minicore: coerce_unsized - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; const GOAL: usize = { let it: &[i32] = &[1, 2, 3]; @@ -48,9 +45,8 @@ fn size_of_val() { //- minicore: coerce_unsized, transmute use core::mem::transmute; - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; struct X { x: i64, @@ -70,9 +66,8 @@ fn size_of_val() { //- minicore: coerce_unsized, transmute use core::mem::transmute; - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; struct X { x: i32, @@ -90,9 +85,8 @@ fn size_of_val() { check_number( r#" //- minicore: coerce_unsized, fmt, builtin_impls, dispatch_from_dyn - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; const GOAL: usize = { let x: &i16 = &5; @@ -106,9 +100,8 @@ fn size_of_val() { check_number( r#" //- minicore: coerce_unsized - extern "rust-intrinsic" { - pub fn size_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn size_of_val(_: *const T) -> usize; const GOAL: usize = { size_of_val("salam") @@ -123,9 +116,8 @@ fn min_align_of_val() { check_number( r#" //- minicore: coerce_unsized - extern "rust-intrinsic" { - pub fn min_align_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn min_align_of_val(_: *const T) -> usize; struct X(i32, u8); @@ -136,9 +128,8 @@ fn min_align_of_val() { check_number( r#" //- minicore: coerce_unsized - extern "rust-intrinsic" { - pub fn min_align_of_val(_: *const T) -> usize; - } + #[rustc_intrinsic] + pub fn min_align_of_val(_: *const T) -> usize; const GOAL: usize = { let x: &[i32] = &[1, 2, 3]; @@ -153,9 +144,8 @@ fn min_align_of_val() { fn type_name() { check_str( r#" - extern "rust-intrinsic" { - pub fn type_name() -> &'static str; - } + #[rustc_intrinsic] + pub fn type_name() -> &'static str; const GOAL: &str = type_name::(); "#, @@ -163,9 +153,8 @@ fn type_name() { ); check_str( r#" - extern "rust-intrinsic" { - pub fn type_name() -> &'static str; - } + #[rustc_intrinsic] + pub fn type_name() -> &'static str; mod mod1 { pub mod mod2 { @@ -183,9 +172,8 @@ fn type_name() { fn transmute() { check_number( r#" - extern "rust-intrinsic" { - pub fn transmute(e: T) -> U; - } + #[rustc_intrinsic] + pub fn transmute(e: T) -> U; const GOAL: i32 = transmute((1i16, 1i16)); "#, @@ -197,10 +185,10 @@ fn transmute() { fn read_via_copy() { check_number( r#" - extern "rust-intrinsic" { - pub fn read_via_copy(e: *const T) -> T; - pub fn volatile_load(e: *const T) -> T; - } + #[rustc_intrinsic] + pub fn read_via_copy(e: *const T) -> T; + #[rustc_intrinsic] + pub fn volatile_load(e: *const T) -> T; const GOAL: i32 = { let x = 2; @@ -399,9 +387,14 @@ fn discriminant_value() { fn likely() { check_number( r#" - extern "rust-intrinsic" { - pub fn likely(b: bool) -> bool; - pub fn unlikely(b: bool) -> bool; + #[rustc_intrinsic] + pub const fn likely(b: bool) -> bool { + b + } + + #[rustc_intrinsic] + pub const fn unlikely(b: bool) -> bool { + b } const GOAL: bool = likely(true) && unlikely(true) && !likely(false) && !unlikely(false); @@ -704,9 +697,8 @@ fn rotate() { ); check_number( r#" - extern "rust-intrinsic" { - pub fn rotate_right(x: T, y: T) -> T; - } + #[rustc_intrinsic] + pub fn rotate_right(x: T, y: T) -> T; const GOAL: i32 = rotate_right(10006016, 1020315); "#, @@ -721,9 +713,8 @@ fn simd() { pub struct i8x16( i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8, ); - extern "platform-intrinsic" { - pub fn simd_bitmask(x: T) -> U; - } + #[rustc_intrinsic] + pub fn simd_bitmask(x: T) -> U; const GOAL: u16 = simd_bitmask(i8x16( 0, 1, 0, 0, 2, 255, 100, 0, 50, 0, 1, 1, 0, 0, 0, 0 )); @@ -735,10 +726,10 @@ fn simd() { pub struct i8x16( i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8, ); - extern "platform-intrinsic" { - pub fn simd_lt(x: T, y: T) -> U; - pub fn simd_bitmask(x: T) -> U; - } + #[rustc_intrinsic] + pub fn simd_lt(x: T, y: T) -> U; + #[rustc_intrinsic] + pub fn simd_bitmask(x: T) -> U; const GOAL: u16 = simd_bitmask(simd_lt::( i8x16( -105, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index c9ab0acc0849..4991d173b9c4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -201,7 +201,7 @@ impl<'a> DeclValidator<'a> { // Don't run the lint on extern "[not Rust]" fn items with the // #[no_mangle] attribute. - let no_mangle = data.attrs.by_key(&sym::no_mangle).exists(); + let no_mangle = self.db.attrs(func.into()).by_key(&sym::no_mangle).exists(); if no_mangle && data.abi.as_ref().is_some_and(|abi| *abi != sym::Rust) { cov_mark::hit!(extern_func_no_mangle_ignored); } else { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs index 0cdad74a4f6c..0a78f4a5b24b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs @@ -9,7 +9,7 @@ use hir_def::{ resolver::HasResolver, }; use hir_expand::name::Name; -use intern::sym; +use intern::{sym, Symbol}; use crate::{ error_lifetime, @@ -54,49 +54,32 @@ impl Evaluator<'_> { } let function_data = self.db.function_data(def); - let is_intrinsic = match &function_data.abi { - Some(abi) => *abi == sym::rust_dash_intrinsic, - None => match def.lookup(self.db.upcast()).container { - hir_def::ItemContainerId::ExternBlockId(block) => { - let id = block.lookup(self.db.upcast()).id; - id.item_tree(self.db.upcast())[id.value].abi.as_ref() - == Some(&sym::rust_dash_intrinsic) - } - _ => false, - }, - }; + let attrs = self.db.attrs(def.into()); + let is_intrinsic = attrs.by_key(&sym::rustc_intrinsic).exists() + // Keep this around for a bit until extern "rustc-intrinsic" abis are no longer used + || (match &function_data.abi { + Some(abi) => *abi == sym::rust_dash_intrinsic, + None => match def.lookup(self.db.upcast()).container { + hir_def::ItemContainerId::ExternBlockId(block) => { + let id = block.lookup(self.db.upcast()).id; + id.item_tree(self.db.upcast())[id.value].abi.as_ref() + == Some(&sym::rust_dash_intrinsic) + } + _ => false, + }, + }); + if is_intrinsic { - self.exec_intrinsic( + return self.exec_intrinsic( function_data.name.as_str(), args, generic_args, destination, locals, span, - )?; - return Ok(true); - } - let is_platform_intrinsic = match &function_data.abi { - Some(abi) => *abi == sym::platform_dash_intrinsic, - None => match def.lookup(self.db.upcast()).container { - hir_def::ItemContainerId::ExternBlockId(block) => { - let id = block.lookup(self.db.upcast()).id; - id.item_tree(self.db.upcast())[id.value].abi.as_ref() - == Some(&sym::platform_dash_intrinsic) - } - _ => false, - }, - }; - if is_platform_intrinsic { - self.exec_platform_intrinsic( - function_data.name.as_str(), - args, - generic_args, - destination, - locals, - span, - )?; - return Ok(true); + !function_data.has_body() + || attrs.by_key(&sym::rustc_intrinsic_must_be_overridden).exists(), + ); } let is_extern_c = match def.lookup(self.db.upcast()).container { hir_def::ItemContainerId::ExternBlockId(block) => { @@ -106,27 +89,25 @@ impl Evaluator<'_> { _ => false, }; if is_extern_c { - self.exec_extern_c( - function_data.name.as_str(), - args, - generic_args, - destination, - locals, - span, - )?; - return Ok(true); + return self + .exec_extern_c( + function_data.name.as_str(), + args, + generic_args, + destination, + locals, + span, + ) + .map(|()| true); } - let alloc_fn = function_data - .attrs - .iter() - .filter_map(|it| it.path().as_ident()) - .map(|it| it.as_str()) - .find(|it| { + + let alloc_fn = + attrs.iter().filter_map(|it| it.path().as_ident()).map(|it| it.symbol()).find(|it| { [ - "rustc_allocator", - "rustc_deallocator", - "rustc_reallocator", - "rustc_allocator_zeroed", + &sym::rustc_allocator, + &sym::rustc_deallocator, + &sym::rustc_reallocator, + &sym::rustc_allocator_zeroed, ] .contains(it) }); @@ -270,12 +251,12 @@ impl Evaluator<'_> { fn exec_alloc_fn( &mut self, - alloc_fn: &str, + alloc_fn: &Symbol, args: &[IntervalAndTy], destination: Interval, ) -> Result<()> { match alloc_fn { - "rustc_allocator_zeroed" | "rustc_allocator" => { + _ if *alloc_fn == sym::rustc_allocator_zeroed || *alloc_fn == sym::rustc_allocator => { let [size, align] = args else { return Err(MirEvalError::InternalError( "rustc_allocator args are not provided".into(), @@ -286,8 +267,8 @@ impl Evaluator<'_> { let result = self.heap_allocate(size, align)?; destination.write_from_bytes(self, &result.to_bytes())?; } - "rustc_deallocator" => { /* no-op for now */ } - "rustc_reallocator" => { + _ if *alloc_fn == sym::rustc_deallocator => { /* no-op for now */ } + _ if *alloc_fn == sym::rustc_reallocator => { let [ptr, old_size, align, new_size] = args else { return Err(MirEvalError::InternalError( "rustc_allocator args are not provided".into(), @@ -603,21 +584,6 @@ impl Evaluator<'_> { } } - fn exec_platform_intrinsic( - &mut self, - name: &str, - args: &[IntervalAndTy], - generic_args: &Substitution, - destination: Interval, - locals: &Locals, - span: MirSpan, - ) -> Result<()> { - if let Some(name) = name.strip_prefix("simd_") { - return self.exec_simd_intrinsic(name, args, generic_args, destination, locals, span); - } - not_supported!("unknown platform intrinsic {name}"); - } - fn exec_intrinsic( &mut self, name: &str, @@ -626,9 +592,17 @@ impl Evaluator<'_> { destination: Interval, locals: &Locals, span: MirSpan, - ) -> Result<()> { + needs_override: bool, + ) -> Result { if let Some(name) = name.strip_prefix("atomic_") { - return self.exec_atomic_intrinsic(name, args, generic_args, destination, locals, span); + return self + .exec_atomic_intrinsic(name, args, generic_args, destination, locals, span) + .map(|()| true); + } + if let Some(name) = name.strip_prefix("simd_") { + return self + .exec_simd_intrinsic(name, args, generic_args, destination, locals, span) + .map(|()| true); } // FIXME(#17451): Add `f16` and `f128` intrinsics. if let Some(name) = name.strip_suffix("f64") { @@ -701,7 +675,7 @@ impl Evaluator<'_> { } _ => not_supported!("unknown f64 intrinsic {name}"), }; - return destination.write_from_bytes(self, &result.to_le_bytes()); + return destination.write_from_bytes(self, &result.to_le_bytes()).map(|()| true); } if let Some(name) = name.strip_suffix("f32") { let result = match name { @@ -773,7 +747,7 @@ impl Evaluator<'_> { } _ => not_supported!("unknown f32 intrinsic {name}"), }; - return destination.write_from_bytes(self, &result.to_le_bytes()); + return destination.write_from_bytes(self, &result.to_le_bytes()).map(|()| true); } match name { "size_of" => { @@ -1146,12 +1120,6 @@ impl Evaluator<'_> { }; destination.write_from_interval(self, arg.interval) } - "likely" | "unlikely" => { - let [arg] = args else { - return Err(MirEvalError::InternalError("likely arg is not provided".into())); - }; - destination.write_from_interval(self, arg.interval) - } "ctpop" => { let [arg] = args else { return Err(MirEvalError::InternalError("ctpop arg is not provided".into())); @@ -1296,7 +1264,7 @@ impl Evaluator<'_> { None, span, )?; - return Ok(()); + return Ok(true); } } not_supported!("FnOnce was not available for executing const_eval_select"); @@ -1349,8 +1317,10 @@ impl Evaluator<'_> { self.write_memory_using_ref(dst, size)?.fill(val); Ok(()) } - _ => not_supported!("unknown intrinsic {name}"), + _ if needs_override => not_supported!("intrinsic {name} is not implemented"), + _ => return Ok(false), } + .map(|()| true) } fn size_align_of_unsized( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 0a436ff2b41a..7429ce3c73e0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -259,25 +259,25 @@ pub fn is_fn_unsafe_to_call(db: &dyn HirDatabase, func: FunctionId) -> bool { return true; } + let is_intrinsic = db.attrs(func.into()).by_key(&sym::rustc_intrinsic).exists() + || data.abi.as_ref() == Some(&sym::rust_dash_intrinsic); + let loc = func.lookup(db.upcast()); match loc.container { hir_def::ItemContainerId::ExternBlockId(block) => { - // Function in an `extern` block are always unsafe to call, except when - // it is marked as `safe` or it has `"rust-intrinsic"` ABI there are a - // few exceptions. - let id = block.lookup(db.upcast()).id; - - let is_intrinsic = - id.item_tree(db.upcast())[id.value].abi.as_ref() == Some(&sym::rust_dash_intrinsic); - - if is_intrinsic { + if is_intrinsic || { + let id = block.lookup(db.upcast()).id; + id.item_tree(db.upcast())[id.value].abi.as_ref() == Some(&sym::rust_dash_intrinsic) + } { // Intrinsics are unsafe unless they have the rustc_safe_intrinsic attribute - !data.attrs.by_key(&sym::rustc_safe_intrinsic).exists() + !db.attrs(func.into()).by_key(&sym::rustc_safe_intrinsic).exists() } else { - // Extern items without `safe` modifier are always unsafe + // Function in an `extern` block are always unsafe to call, except when + // it is marked as `safe`. !data.is_safe() } } + _ if is_intrinsic => !db.attrs(func.into()).by_key(&sym::rustc_safe_intrinsic).exists(), _ => false, } } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index ebd84fd2be2a..8c3b7a6d3cf5 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2246,35 +2246,33 @@ impl Function { /// Does this function have `#[test]` attribute? pub fn is_test(self, db: &dyn HirDatabase) -> bool { - db.function_data(self.id).attrs.is_test() + db.attrs(self.id.into()).is_test() } /// is this a `fn main` or a function with an `export_name` of `main`? pub fn is_main(self, db: &dyn HirDatabase) -> bool { - let data = db.function_data(self.id); - data.attrs.export_name() == Some(&sym::main) - || self.module(db).is_crate_root() && data.name == sym::main + db.attrs(self.id.into()).export_name() == Some(&sym::main) + || self.module(db).is_crate_root() && db.function_data(self.id).name == sym::main } /// Is this a function with an `export_name` of `main`? pub fn exported_main(self, db: &dyn HirDatabase) -> bool { - let data = db.function_data(self.id); - data.attrs.export_name() == Some(&sym::main) + db.attrs(self.id.into()).export_name() == Some(&sym::main) } /// Does this function have the ignore attribute? pub fn is_ignore(self, db: &dyn HirDatabase) -> bool { - db.function_data(self.id).attrs.is_ignore() + db.attrs(self.id.into()).is_ignore() } /// Does this function have `#[bench]` attribute? pub fn is_bench(self, db: &dyn HirDatabase) -> bool { - db.function_data(self.id).attrs.is_bench() + db.attrs(self.id.into()).is_bench() } /// Is this function marked as unstable with `#[feature]` attribute? pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.function_data(self.id).attrs.is_unstable() + db.attrs(self.id.into()).is_unstable() } pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool { @@ -2289,8 +2287,7 @@ impl Function { } pub fn as_proc_macro(self, db: &dyn HirDatabase) -> Option { - let function_data = db.function_data(self.id); - let attrs = &function_data.attrs; + let attrs = db.attrs(self.id.into()); // FIXME: Store this in FunctionData flags? if !(attrs.is_proc_macro() || attrs.is_proc_macro_attribute() diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs index aecafb444c30..865518fe941e 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -392,18 +392,22 @@ define_symbols! { rust_2024, rust_analyzer, Rust, + rustc_allocator_zeroed, + rustc_allocator, rustc_allow_incoherent_impl, rustc_builtin_macro, rustc_coherence_is_core, rustc_const_panic_str, + rustc_deallocator, rustc_deprecated_safe_2024, rustc_has_incoherent_inherent_impls, - rustc_intrinsic, rustc_intrinsic_must_be_overridden, + rustc_intrinsic, rustc_layout_scalar_valid_range_end, rustc_layout_scalar_valid_range_start, rustc_legacy_const_generics, rustc_macro_transparency, + rustc_reallocator, rustc_reservation_impl, rustc_safe_intrinsic, rustc_skip_array_during_method_dispatch, diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 67629fcf7cc5..07767d5ae9f6 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -370,15 +370,13 @@ pub mod mem { // endregion:drop // region:transmute - extern "rust-intrinsic" { - pub fn transmute(src: Src) -> Dst; - } + #[rustc_intrinsic] + pub fn transmute(src: Src) -> Dst; // endregion:transmute // region:size_of - extern "rust-intrinsic" { - pub fn size_of() -> usize; - } + #[rustc_intrinsic] + pub fn size_of() -> usize; // endregion:size_of // region:discriminant From 1330cd1888ca1cb7ae2785dc1a18d55d3a3f21b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 4 Nov 2024 15:23:05 +0200 Subject: [PATCH 030/214] Don't try to auto-publish text-edit --- src/tools/rust-analyzer/.github/workflows/autopublish.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml index e0135a0269a3..5258d9ddd3ad 100644 --- a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml +++ b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml @@ -53,7 +53,6 @@ jobs: cargo workspaces rename --from project-model project_model cargo workspaces rename --from test-fixture test_fixture cargo workspaces rename --from test-utils test_utils - cargo workspaces rename --from text-edit text_edit # Remove library crates from the workspaces so we don't auto-publish them as well sed -i 's/ "lib\/\*",//' ./Cargo.toml cargo workspaces rename ra_ap_%n From 0bc622d251f8606ca86d4a5ccdbedd52afef247a Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 31 Oct 2024 23:03:09 +0100 Subject: [PATCH 031/214] Prefer `pub(super)` in `unreachable_pub` lint suggestion --- compiler/rustc_lint/src/builtin.rs | 18 ++++ compiler/rustc_lint/src/lints.rs | 3 +- tests/ui/lint/unreachable_pub.fixed | 116 ++++++++++++++++++++++ tests/ui/lint/unreachable_pub.rs | 46 +++++++++ tests/ui/lint/unreachable_pub.stderr | 140 ++++++++++++++++++++++++--- 5 files changed, 307 insertions(+), 16 deletions(-) create mode 100644 tests/ui/lint/unreachable_pub.fixed diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 02d22ee49bd6..130f3cb7c2a7 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1298,12 +1298,30 @@ impl UnreachablePub { let mut applicability = Applicability::MachineApplicable; if cx.tcx.visibility(def_id).is_public() && !cx.effective_visibilities.is_reachable(def_id) { + // prefer suggesting `pub(super)` instead of `pub(crate)` when possible, + // except when `pub(super) == pub(crate)` + let new_vis = if let Some(ty::Visibility::Restricted(restricted_did)) = + cx.effective_visibilities.effective_vis(def_id).map(|effective_vis| { + effective_vis.at_level(rustc_middle::middle::privacy::Level::Reachable) + }) + && let parent_parent = cx.tcx.parent_module_from_def_id( + cx.tcx.parent_module_from_def_id(def_id.into()).into(), + ) + && *restricted_did == parent_parent.to_local_def_id() + && !restricted_did.to_def_id().is_crate_root() + { + "pub(super)" + } else { + "pub(crate)" + }; + if vis_span.from_expansion() { applicability = Applicability::MaybeIncorrect; } let def_span = cx.tcx.def_span(def_id); cx.emit_span_lint(UNREACHABLE_PUB, def_span, BuiltinUnreachablePub { what, + new_vis, suggestion: (vis_span, applicability), help: exportable, }); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 38e52570e53e..352155729e51 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -254,7 +254,8 @@ impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { #[diag(lint_builtin_unreachable_pub)] pub(crate) struct BuiltinUnreachablePub<'a> { pub what: &'a str, - #[suggestion(code = "pub(crate)")] + pub new_vis: &'a str, + #[suggestion(code = "{new_vis}")] pub suggestion: (Span, Applicability), #[help] pub help: bool, diff --git a/tests/ui/lint/unreachable_pub.fixed b/tests/ui/lint/unreachable_pub.fixed new file mode 100644 index 000000000000..163e8d24b325 --- /dev/null +++ b/tests/ui/lint/unreachable_pub.fixed @@ -0,0 +1,116 @@ +//@ check-pass +//@ edition: 2018 +//@ run-rustfix + +#![allow(unused)] +#![warn(unreachable_pub)] + +mod private_mod { + // non-leaked `pub` items in private module should be linted + pub(crate) use std::fmt; //~ WARNING unreachable_pub + pub(crate) use std::env::{Args}; // braced-use has different item spans than unbraced + //~^ WARNING unreachable_pub + + // we lint on struct definition + pub(crate) struct Hydrogen { //~ WARNING unreachable_pub + // but not on fields, even if they are `pub` as putting `pub(crate)` + // it would clutter the source code for little value + pub neutrons: usize, + pub(crate) electrons: usize + } + pub(crate) struct Calcium { + pub neutrons: usize, + } + impl Hydrogen { + // impls, too + pub(crate) fn count_neutrons(&self) -> usize { self.neutrons } //~ WARNING unreachable_pub + pub(crate) fn count_electrons(&self) -> usize { self.electrons } + } + impl Clone for Hydrogen { + fn clone(&self) -> Hydrogen { + Hydrogen { neutrons: self.neutrons, electrons: self.electrons } + } + } + + pub(crate) enum Helium {} //~ WARNING unreachable_pub + pub(crate) union Lithium { c1: usize, c2: u8 } //~ WARNING unreachable_pub + pub(crate) fn beryllium() {} //~ WARNING unreachable_pub + pub(crate) trait Boron {} //~ WARNING unreachable_pub + pub(crate) const CARBON: usize = 1; //~ WARNING unreachable_pub + pub(crate) static NITROGEN: usize = 2; //~ WARNING unreachable_pub + pub(crate) type Oxygen = bool; //~ WARNING unreachable_pub + + macro_rules! define_empty_struct_with_visibility { + ($visibility: vis, $name: ident) => { $visibility struct $name {} } + //~^ WARNING unreachable_pub + } + define_empty_struct_with_visibility!(pub(crate), Fluorine); + + extern "C" { + pub(crate) fn catalyze() -> bool; //~ WARNING unreachable_pub + } + + mod private_in_private { + pub(super) enum Helium {} //~ WARNING unreachable_pub + pub(super) fn beryllium() {} //~ WARNING unreachable_pub + } + + pub(crate) mod crate_in_private { + pub(crate) const CARBON: usize = 1; //~ WARNING unreachable_pub + } + + pub(crate) mod pub_in_private { //~ WARNING unreachable_pub + pub(crate) static NITROGEN: usize = 2; //~ WARNING unreachable_pub + } + + fn foo() { + const { + pub(crate) struct Foo; //~ WARNING unreachable_pub + }; + } + + enum Weird { + Variant = { + pub(crate) struct Foo; //~ WARNING unreachable_pub + + mod tmp { + pub(crate) struct Bar; //~ WARNING unreachable_pub + } + + let _ = tmp::Bar; + + 0 + }, + } + + pub(crate) use fpu_precision::set_precision; //~ WARNING unreachable_pub + + mod fpu_precision { + pub(crate) fn set_precision() {} //~ WARNING unreachable_pub + pub(super) fn set_micro_precision() {} //~ WARNING unreachable_pub + } + + // items leaked through signatures (see `get_neon` below) are OK + pub struct Neon {} + + // crate-visible items are OK + pub(crate) struct Sodium {} +} + +pub mod public_mod { + // module is public: these are OK, too + pub struct Magnesium {} + pub(crate) struct Aluminum {} +} + +pub fn get_neon() -> private_mod::Neon { + private_mod::Neon {} +} + +fn main() { + let _ = get_neon(); + let _ = private_mod::beryllium(); + let _ = private_mod::crate_in_private::CARBON; + let _ = private_mod::pub_in_private::NITROGEN; + let _ = unsafe { private_mod::catalyze() }; +} diff --git a/tests/ui/lint/unreachable_pub.rs b/tests/ui/lint/unreachable_pub.rs index f21f6640342c..8524820bcfbc 100644 --- a/tests/ui/lint/unreachable_pub.rs +++ b/tests/ui/lint/unreachable_pub.rs @@ -1,4 +1,6 @@ //@ check-pass +//@ edition: 2018 +//@ run-rustfix #![allow(unused)] #![warn(unreachable_pub)] @@ -48,6 +50,46 @@ mod private_mod { pub fn catalyze() -> bool; //~ WARNING unreachable_pub } + mod private_in_private { + pub enum Helium {} //~ WARNING unreachable_pub + pub fn beryllium() {} //~ WARNING unreachable_pub + } + + pub(crate) mod crate_in_private { + pub const CARBON: usize = 1; //~ WARNING unreachable_pub + } + + pub mod pub_in_private { //~ WARNING unreachable_pub + pub static NITROGEN: usize = 2; //~ WARNING unreachable_pub + } + + fn foo() { + const { + pub struct Foo; //~ WARNING unreachable_pub + }; + } + + enum Weird { + Variant = { + pub struct Foo; //~ WARNING unreachable_pub + + mod tmp { + pub struct Bar; //~ WARNING unreachable_pub + } + + let _ = tmp::Bar; + + 0 + }, + } + + pub use fpu_precision::set_precision; //~ WARNING unreachable_pub + + mod fpu_precision { + pub fn set_precision() {} //~ WARNING unreachable_pub + pub fn set_micro_precision() {} //~ WARNING unreachable_pub + } + // items leaked through signatures (see `get_neon` below) are OK pub struct Neon {} @@ -67,4 +109,8 @@ pub fn get_neon() -> private_mod::Neon { fn main() { let _ = get_neon(); + let _ = private_mod::beryllium(); + let _ = private_mod::crate_in_private::CARBON; + let _ = private_mod::pub_in_private::NITROGEN; + let _ = unsafe { private_mod::catalyze() }; } diff --git a/tests/ui/lint/unreachable_pub.stderr b/tests/ui/lint/unreachable_pub.stderr index 65f45fbd8168..5173ff1f0264 100644 --- a/tests/ui/lint/unreachable_pub.stderr +++ b/tests/ui/lint/unreachable_pub.stderr @@ -1,5 +1,5 @@ warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:8:13 + --> $DIR/unreachable_pub.rs:10:13 | LL | pub use std::fmt; | --- ^^^^^^^^ @@ -8,13 +8,13 @@ LL | pub use std::fmt; | = help: or consider exporting it for use by other crates note: the lint level is defined here - --> $DIR/unreachable_pub.rs:4:9 + --> $DIR/unreachable_pub.rs:6:9 | LL | #![warn(unreachable_pub)] | ^^^^^^^^^^^^^^^ warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:9:24 + --> $DIR/unreachable_pub.rs:11:24 | LL | pub use std::env::{Args}; // braced-use has different item spans than unbraced | --- ^^^^ @@ -24,7 +24,7 @@ LL | pub use std::env::{Args}; // braced-use has different item spans than u = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:13:5 + --> $DIR/unreachable_pub.rs:15:5 | LL | pub struct Hydrogen { | ---^^^^^^^^^^^^^^^^ @@ -34,7 +34,7 @@ LL | pub struct Hydrogen { = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:24:9 + --> $DIR/unreachable_pub.rs:26:9 | LL | pub fn count_neutrons(&self) -> usize { self.neutrons } | ---^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,7 +42,7 @@ LL | pub fn count_neutrons(&self) -> usize { self.neutrons } | help: consider restricting its visibility: `pub(crate)` warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:33:5 + --> $DIR/unreachable_pub.rs:35:5 | LL | pub enum Helium {} | ---^^^^^^^^^^^^ @@ -52,7 +52,7 @@ LL | pub enum Helium {} = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:34:5 + --> $DIR/unreachable_pub.rs:36:5 | LL | pub union Lithium { c1: usize, c2: u8 } | ---^^^^^^^^^^^^^^ @@ -62,7 +62,7 @@ LL | pub union Lithium { c1: usize, c2: u8 } = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:35:5 + --> $DIR/unreachable_pub.rs:37:5 | LL | pub fn beryllium() {} | ---^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | pub fn beryllium() {} = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:36:5 + --> $DIR/unreachable_pub.rs:38:5 | LL | pub trait Boron {} | ---^^^^^^^^^^^^ @@ -82,7 +82,7 @@ LL | pub trait Boron {} = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:37:5 + --> $DIR/unreachable_pub.rs:39:5 | LL | pub const CARBON: usize = 1; | ---^^^^^^^^^^^^^^^^^^^^ @@ -92,7 +92,7 @@ LL | pub const CARBON: usize = 1; = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:38:5 + --> $DIR/unreachable_pub.rs:40:5 | LL | pub static NITROGEN: usize = 2; | ---^^^^^^^^^^^^^^^^^^^^^^^ @@ -102,7 +102,7 @@ LL | pub static NITROGEN: usize = 2; = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:39:5 + --> $DIR/unreachable_pub.rs:41:5 | LL | pub type Oxygen = bool; | ---^^^^^^^^^^^^ @@ -112,7 +112,7 @@ LL | pub type Oxygen = bool; = help: or consider exporting it for use by other crates warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:42:47 + --> $DIR/unreachable_pub.rs:44:47 | LL | ($visibility: vis, $name: ident) => { $visibility struct $name {} } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -127,7 +127,7 @@ LL | define_empty_struct_with_visibility!(pub, Fluorine); = note: this warning originates in the macro `define_empty_struct_with_visibility` (in Nightly builds, run with -Z macro-backtrace for more info) warning: unreachable `pub` item - --> $DIR/unreachable_pub.rs:48:9 + --> $DIR/unreachable_pub.rs:50:9 | LL | pub fn catalyze() -> bool; | ---^^^^^^^^^^^^^^^^^^^^^^^ @@ -136,5 +136,115 @@ LL | pub fn catalyze() -> bool; | = help: or consider exporting it for use by other crates -warning: 13 warnings emitted +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:62:5 + | +LL | pub mod pub_in_private { + | ---^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:68:13 + | +LL | pub struct Foo; + | ---^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:74:13 + | +LL | pub struct Foo; + | ---^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:86:13 + | +LL | pub use fpu_precision::set_precision; + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:54:9 + | +LL | pub enum Helium {} + | ---^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(super)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:55:9 + | +LL | pub fn beryllium() {} + | ---^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(super)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:59:9 + | +LL | pub const CARBON: usize = 1; + | ---^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:63:9 + | +LL | pub static NITROGEN: usize = 2; + | ---^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:77:17 + | +LL | pub struct Bar; + | ---^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:89:9 + | +LL | pub fn set_precision() {} + | ---^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(crate)` + | + = help: or consider exporting it for use by other crates + +warning: unreachable `pub` item + --> $DIR/unreachable_pub.rs:90:9 + | +LL | pub fn set_micro_precision() {} + | ---^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider restricting its visibility: `pub(super)` + | + = help: or consider exporting it for use by other crates + +warning: 24 warnings emitted From 59ee3bd9485db9e1d75671d2c599aa46ed51b2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 4 Nov 2024 21:35:10 +0200 Subject: [PATCH 032/214] Enable triagebot transfer feature --- src/tools/rust-analyzer/triagebot.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/triagebot.toml b/src/tools/rust-analyzer/triagebot.toml index d62adb9144ba..1138035d0e82 100644 --- a/src/tools/rust-analyzer/triagebot.toml +++ b/src/tools/rust-analyzer/triagebot.toml @@ -19,3 +19,5 @@ exclude_titles = [ # exclude syncs from subtree in rust-lang/rust "sync from rust", ] labels = ["has-merge-commits", "S-waiting-on-author"] + +[transfer] From 1b7239d9548d5d6672ac05ba1f48019a29b9790d Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:19:58 -0800 Subject: [PATCH 033/214] Fixing a missed check for needs_parenthesis in division --- clippy_lints/src/operators/identity_op.rs | 9 ++------- tests/ui/identity_op.fixed | 3 +++ tests/ui/identity_op.rs | 3 +++ tests/ui/identity_op.stderr | 18 ++++++++++++------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index e3ce1ae9427d..769937857276 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -67,13 +67,8 @@ pub(crate) fn check<'tcx>( }, BinOpKind::Div => { if is_redundant_op(cx, right, 1) { - span_ineffective_operation( - cx, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::BitAnd => { diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 927142d5f802..b80ba701c3ad 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -232,6 +232,9 @@ fn issue_13470() { // If we don't maintain the parens here, the behavior changes let _ = -(x + y); //~^ ERROR: this operation has no effect + // Similarly, we need to maintain parens here + let _ = -(x / y); + //~^ ERROR: this operation has no effect // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y); //~^ ERROR: this operation has no effect diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index a50546929a82..3e20fa6f2b89 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -232,6 +232,9 @@ fn issue_13470() { // If we don't maintain the parens here, the behavior changes let _ = -(x + y + 0i32); //~^ ERROR: this operation has no effect + // Similarly, we need to maintain parens here + let _ = -(x / y / 1i32); + //~^ ERROR: this operation has no effect // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y + 0i32); //~^ ERROR: this operation has no effect diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 79d1a7bd81c3..3f5cab5404d0 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -344,34 +344,40 @@ LL | let _ = -(x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:236:20 + --> tests/ui/identity_op.rs:236:14 + | +LL | let _ = -(x / y / 1i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x / y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:239:20 | LL | let _ = 2i32 * (x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:240:20 + --> tests/ui/identity_op.rs:243:20 | LL | let _ = 2i32 - (x - y - 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x - y)` error: this operation has no effect - --> tests/ui/identity_op.rs:244:17 + --> tests/ui/identity_op.rs:247:17 | LL | let _ = 2 + (x + (y * z) + 0); | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` error: this operation has no effect - --> tests/ui/identity_op.rs:248:20 + --> tests/ui/identity_op.rs:251:20 | LL | let _ = 2i32 + (x * y * 1i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` error: this operation has no effect - --> tests/ui/identity_op.rs:253:25 + --> tests/ui/identity_op.rs:256:25 | LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` -error: aborting due to 62 previous errors +error: aborting due to 63 previous errors From 15a71b64b837373b205daaebcf75e179fdfec89e Mon Sep 17 00:00:00 2001 From: Adwin White Date: Tue, 5 Nov 2024 12:41:52 +0800 Subject: [PATCH 034/214] cleanup: Remove outdated comment and logic of `thir_body` --- compiler/rustc_middle/src/query/mod.rs | 2 +- compiler/rustc_mir_build/src/check_unsafety.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 41d0b47388cc..30a2e839fb42 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -463,7 +463,7 @@ rustc_queries! { separate_provide_extern } - /// Fetch the THIR for a given body. If typeck for that body failed, returns an empty `Thir`. + /// Fetch the THIR for a given body. query thir_body(key: LocalDefId) -> Result<(&'tcx Steal>, thir::ExprId), ErrorGuaranteed> { // Perf tests revealed that hashing THIR is inefficient (see #85729). no_hash diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index f3e6301d9d16..33e194fa2462 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -1005,10 +1005,6 @@ pub(crate) fn check_unsafety(tcx: TyCtxt<'_>, def: LocalDefId) { // Runs all other queries that depend on THIR. tcx.ensure_with_value().mir_built(def); let thir = &thir.steal(); - // If `thir` is empty, a type error occurred, skip this body. - if thir.exprs.is_empty() { - return; - } let hir_id = tcx.local_def_id_to_hir_id(def); let safety_context = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(SafetyContext::Safe, |fn_sig| { From 966f4b3b0c906838e689a438a06b9ade4f839e5c Mon Sep 17 00:00:00 2001 From: Henry Jiang Date: Tue, 5 Nov 2024 12:58:37 -0500 Subject: [PATCH 035/214] add run-make support for aix --- src/tools/compiletest/src/directive-list.rs | 1 + src/tools/run-make-support/src/external_deps/rustc.rs | 5 ++++- src/tools/run-make-support/src/targets.rs | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs index 980b3f6829a8..446e90978f6b 100644 --- a/src/tools/compiletest/src/directive-list.rs +++ b/src/tools/compiletest/src/directive-list.rs @@ -35,6 +35,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-64bit", "ignore-aarch64", "ignore-aarch64-unknown-linux-gnu", + "ignore-aix", "ignore-android", "ignore-apple", "ignore-arm", diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index 35d983dc607f..c2989aedee1f 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -5,7 +5,7 @@ use crate::command::Command; use crate::env::env_var; use crate::path_helpers::cwd; use crate::util::set_host_rpath; -use crate::{is_darwin, is_msvc, is_windows, uname}; +use crate::{is_aix, is_darwin, is_msvc, is_windows, uname}; /// Construct a new `rustc` invocation. This will automatically set the library /// search path as `-L cwd()`. Use [`bare_rustc`] to avoid this. @@ -365,6 +365,9 @@ impl Rustc { if is_msvc() { None } else { Some("-lstatic:-bundle=stdc++") } } else if is_darwin() { Some("-lc++") + } else if is_aix() { + self.cmd.arg("-lc++"); + Some("-lc++abi") } else { match &uname()[..] { "FreeBSD" | "SunOS" | "OpenBSD" => None, diff --git a/src/tools/run-make-support/src/targets.rs b/src/tools/run-make-support/src/targets.rs index 896abb73fc10..ae004fd0cbdd 100644 --- a/src/tools/run-make-support/src/targets.rs +++ b/src/tools/run-make-support/src/targets.rs @@ -28,6 +28,12 @@ pub fn is_darwin() -> bool { target().contains("darwin") } +/// Check if target uses AIX. +#[must_use] +pub fn is_aix() -> bool { + target().contains("aix") +} + /// Get the target OS on Apple operating systems. #[must_use] pub fn apple_os() -> &'static str { From 1d6e8476746d79d21bde97243c30937738750227 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 5 Nov 2024 18:23:11 +0000 Subject: [PATCH 036/214] Check for both `StmtKind::MacCall` and `ExprKind::MacCall` --- compiler/rustc_ast/src/ast.rs | 35 +- compiler/rustc_resolve/src/def_collector.rs | 29 +- tests/crashes/131915.rs | 13 - .../const_arg_trivial_macro_expansion-2.rs | 20 + ...const_arg_trivial_macro_expansion-2.stderr | 39 ++ .../const_arg_trivial_macro_expansion.rs | 366 ++++++++++++++++++ 6 files changed, 466 insertions(+), 36 deletions(-) delete mode 100644 tests/crashes/131915.rs create mode 100644 tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.rs create mode 100644 tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.stderr create mode 100644 tests/ui/const-generics/early/const_arg_trivial_macro_expansion.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 997c44cffff0..5f71fb97d768 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1194,7 +1194,7 @@ impl Expr { /// /// Does not ensure that the path resolves to a const param, the caller should check this. pub fn is_potential_trivial_const_arg(&self, strip_identity_block: bool) -> bool { - let this = if strip_identity_block { self.maybe_unwrap_block().1 } else { self }; + let this = if strip_identity_block { self.maybe_unwrap_block() } else { self }; if let ExprKind::Path(None, path) = &this.kind && path.is_potential_trivial_const_arg() @@ -1206,14 +1206,41 @@ impl Expr { } /// Returns an expression with (when possible) *one* outter brace removed - pub fn maybe_unwrap_block(&self) -> (bool, &Expr) { + pub fn maybe_unwrap_block(&self) -> &Expr { if let ExprKind::Block(block, None) = &self.kind && let [stmt] = block.stmts.as_slice() && let StmtKind::Expr(expr) = &stmt.kind { - (true, expr) + expr } else { - (false, self) + self + } + } + + /// Determines whether this expression is a macro call optionally wrapped in braces . If + /// `already_stripped_block` is set then we do not attempt to peel off a layer of braces. + /// + /// Returns the [`NodeId`] of the macro call and whether a layer of braces has been peeled + /// either before, or part of, this function. + pub fn optionally_braced_mac_call( + &self, + already_stripped_block: bool, + ) -> Option<(bool, NodeId)> { + match &self.kind { + ExprKind::Block(block, None) + if let [stmt] = &*block.stmts + && !already_stripped_block => + { + match &stmt.kind { + StmtKind::MacCall(_) => Some((true, stmt.id)), + StmtKind::Expr(expr) if let ExprKind::MacCall(_) = &expr.kind => { + Some((true, expr.id)) + } + _ => None, + } + } + ExprKind::MacCall(_) => Some((already_stripped_block, self.id)), + _ => None, } } diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index a825458dc894..bf27b767a497 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -130,18 +130,16 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { &self, anon_const: &'a AnonConst, ) -> Option<(PendingAnonConstInfo, NodeId)> { - let (block_was_stripped, expr) = anon_const.value.maybe_unwrap_block(); - match expr { - Expr { kind: ExprKind::MacCall(..), id, .. } => Some(( + anon_const.value.optionally_braced_mac_call(false).map(|(block_was_stripped, id)| { + ( PendingAnonConstInfo { id: anon_const.id, span: anon_const.value.span, block_was_stripped, }, - *id, - )), - _ => None, - } + id, + ) + }) } /// Determines whether the expression `const_arg_sub_expr` is a simple macro call, sometimes @@ -161,18 +159,11 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { panic!("Checking expr is trivial macro call without having entered anon const: `{const_arg_sub_expr:?}`"), ); - let (block_was_stripped, expr) = if pending_anon.block_was_stripped { - (true, const_arg_sub_expr) - } else { - const_arg_sub_expr.maybe_unwrap_block() - }; - - match expr { - Expr { kind: ExprKind::MacCall(..), id, .. } => { - Some((PendingAnonConstInfo { block_was_stripped, ..pending_anon }, *id)) - } - _ => None, - } + const_arg_sub_expr.optionally_braced_mac_call(pending_anon.block_was_stripped).map( + |(block_was_stripped, id)| { + (PendingAnonConstInfo { block_was_stripped, ..pending_anon }, id) + }, + ) } } diff --git a/tests/crashes/131915.rs b/tests/crashes/131915.rs deleted file mode 100644 index 58d45adcb3be..000000000000 --- a/tests/crashes/131915.rs +++ /dev/null @@ -1,13 +0,0 @@ -//@ known-bug: #131915 - -macro_rules! y { - ( $($matcher:tt)*) => { - x - }; -} - -const _: A< - { - y! { test.tou8 } - }, ->; diff --git a/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.rs b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.rs new file mode 100644 index 000000000000..bce7ac5708a5 --- /dev/null +++ b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.rs @@ -0,0 +1,20 @@ +// Regression test for #131915 where we did not handle macro calls as +// statements correctly when determining if a const argument should +// have a `DefId` created or not. + +macro_rules! y { + ( $($matcher:tt)*) => { + x + //~^ ERROR: cannot find value `x` in this scope + }; +} + +const _: A< + //~^ ERROR: free constant item without body + //~| ERROR: cannot find type `A` in this scope + { + y! { test.tou8 } + }, +>; + +fn main() {} diff --git a/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.stderr b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.stderr new file mode 100644 index 000000000000..a3211b776239 --- /dev/null +++ b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion-2.stderr @@ -0,0 +1,39 @@ +error: free constant item without body + --> $DIR/const_arg_trivial_macro_expansion-2.rs:12:1 + | +LL | / const _: A< +LL | | +LL | | +LL | | { +LL | | y! { test.tou8 } +LL | | }, +LL | | >; + | | ^ help: provide a definition for the constant: `= ;` + | |__| + | + +error[E0412]: cannot find type `A` in this scope + --> $DIR/const_arg_trivial_macro_expansion-2.rs:12:10 + | +LL | const _: A< + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/const_arg_trivial_macro_expansion-2.rs:7:9 + | +LL | x + | ^ not found in this scope +... +LL | y! { test.tou8 } + | ---------------- in this macro invocation + | + = note: this error originates in the macro `y` (in Nightly builds, run with -Z macro-backtrace for more info) +help: you might be missing a const parameter + | +LL | const _: A< + | +++++++++++++++++++++ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0412, E0425. +For more information about an error, try `rustc --explain E0412`. diff --git a/tests/ui/const-generics/early/const_arg_trivial_macro_expansion.rs b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion.rs new file mode 100644 index 000000000000..2fdd703ab6f3 --- /dev/null +++ b/tests/ui/const-generics/early/const_arg_trivial_macro_expansion.rs @@ -0,0 +1,366 @@ +//@ known-bug: #132647 +//@ dont-check-compiler-stderr +#![allow(unused_braces)] + +// FIXME(bootstrap): This isn't a known bug, we just don't want to write any error annotations. +// this is hard because macro expansion errors have their span be inside the *definition* of the +// macro rather than the line *invoking* it. This means we would wind up with hundreds of error +// annotations on the macro definitions below rather than on any of the actual lines +// that act as a "test". +// +// It's also made more complicated by the fact that compiletest generates "extra" expected +// notes to give an assertable macro backtrace as otherwise there would *nothing* to annotate +// on the actual test lines. All of these extra notes result in needing to write hundreds of +// unnecessary notes on almost every line in this file. +// +// Even though this is marked `known-bug` it should still fail if this test starts ICEing which +// is "enough" in this case. + +// Test that we correctly create definitions for anon consts even when +// the trivial-ness of the expression is obscured by macro expansions. +// +// Acts as a regression test for: #131915 130321 128016 + +// macros expanding to idents + +macro_rules! unbraced_ident { + () => { + ident + }; +} + +macro_rules! braced_ident { + () => {{ ident }}; +} + +macro_rules! unbraced_unbraced_ident { + () => { + unbraced_ident!() + }; +} + +macro_rules! braced_unbraced_ident { + () => {{ unbraced_ident!() }}; +} + +macro_rules! unbraced_braced_ident { + () => { + braced_ident!() + }; +} + +macro_rules! braced_braced_ident { + () => {{ braced_ident!() }}; +} + +// macros expanding to complex expr + +macro_rules! unbraced_expr { + () => { + ident.other + }; +} + +macro_rules! braced_expr { + () => {{ ident.otherent }}; +} + +macro_rules! unbraced_unbraced_expr { + () => { + unbraced_expr!() + }; +} + +macro_rules! braced_unbraced_expr { + () => {{ unbraced_expr!() }}; +} + +macro_rules! unbraced_braced_expr { + () => { + braced_expr!() + }; +} + +macro_rules! braced_braced_expr { + () => {{ braced_expr!() }}; +} + +#[rustfmt::skip] +mod array_paren_call { + // Arrays where the expanded result is a `Res::Err` + fn array_0() -> [(); unbraced_unbraced_ident!()] { loop {} } + fn array_1() -> [(); braced_unbraced_ident!()] { loop {} } + fn array_2() -> [(); unbraced_braced_ident!()] { loop {} } + fn array_3() -> [(); braced_braced_ident!()] { loop {} } + fn array_4() -> [(); { unbraced_unbraced_ident!() }] { loop {} } + fn array_5() -> [(); { braced_unbraced_ident!() }] { loop {} } + fn array_6() -> [(); { unbraced_braced_ident!() }] { loop {} } + fn array_7() -> [(); { braced_braced_ident!() }] { loop {} } + fn array_8() -> [(); unbraced_ident!()] { loop {} } + fn array_9() -> [(); braced_ident!()] { loop {} } + fn array_10() -> [(); { unbraced_ident!() }] { loop {} } + fn array_11() -> [(); { braced_ident!() }] { loop {} } + + // Arrays where the expanded result is a `Res::ConstParam` + fn array_12() -> [(); unbraced_unbraced_ident!()] { loop {} } + fn array_13() -> [(); braced_unbraced_ident!()] { loop {} } + fn array_14() -> [(); unbraced_braced_ident!()] { loop {} } + fn array_15() -> [(); braced_braced_ident!()] { loop {} } + fn array_16() -> [(); { unbraced_unbraced_ident!() }] { loop {} } + fn array_17() -> [(); { braced_unbraced_ident!() }] { loop {} } + fn array_18() -> [(); { unbraced_braced_ident!() }] { loop {} } + fn array_19() -> [(); { braced_braced_ident!() }] { loop {} } + fn array_20() -> [(); unbraced_ident!()] { loop {} } + fn array_21() -> [(); braced_ident!()] { loop {} } + fn array_22() -> [(); { unbraced_ident!() }] { loop {} } + fn array_23() -> [(); { braced_ident!() }] { loop {} } + + // Arrays where the expanded result is a complex expr + fn array_24() -> [(); unbraced_unbraced_expr!()] { loop {} } + fn array_25() -> [(); braced_unbraced_expr!()] { loop {} } + fn array_26() -> [(); unbraced_braced_expr!()] { loop {} } + fn array_27() -> [(); braced_braced_expr!()] { loop {} } + fn array_28() -> [(); { unbraced_unbraced_expr!() }] { loop {} } + fn array_29() -> [(); { braced_unbraced_expr!() }] { loop {} } + fn array_30() -> [(); { unbraced_braced_expr!() }] { loop {} } + fn array_31() -> [(); { braced_braced_expr!() }] { loop {} } + fn array_32() -> [(); unbraced_expr!()] { loop {} } + fn array_33() -> [(); braced_expr!()] { loop {} } + fn array_34() -> [(); { unbraced_expr!() }] { loop {} } + fn array_35() -> [(); { braced_expr!() }] { loop {} } +} + +#[rustfmt::skip] +mod array_brace_call { + // Arrays where the expanded result is a `Res::Err` + fn array_0() -> [(); unbraced_unbraced_ident!{}] { loop {} } + fn array_1() -> [(); braced_unbraced_ident!{}] { loop {} } + fn array_2() -> [(); unbraced_braced_ident!{}] { loop {} } + fn array_3() -> [(); braced_braced_ident!{}] { loop {} } + fn array_4() -> [(); { unbraced_unbraced_ident!{} }] { loop {} } + fn array_5() -> [(); { braced_unbraced_ident!{} }] { loop {} } + fn array_6() -> [(); { unbraced_braced_ident!{} }] { loop {} } + fn array_7() -> [(); { braced_braced_ident!{} }] { loop {} } + fn array_8() -> [(); unbraced_ident!{}] { loop {} } + fn array_9() -> [(); braced_ident!{}] { loop {} } + fn array_10() -> [(); { unbraced_ident!{} }] { loop {} } + fn array_11() -> [(); { braced_ident!{} }] { loop {} } + + // Arrays where the expanded result is a `Res::ConstParam` + fn array_12() -> [(); unbraced_unbraced_ident!{}] { loop {} } + fn array_13() -> [(); braced_unbraced_ident!{}] { loop {} } + fn array_14() -> [(); unbraced_braced_ident!{}] { loop {} } + fn array_15() -> [(); braced_braced_ident!{}] { loop {} } + fn array_16() -> [(); { unbraced_unbraced_ident!{} }] { loop {} } + fn array_17() -> [(); { braced_unbraced_ident!{} }] { loop {} } + fn array_18() -> [(); { unbraced_braced_ident!{} }] { loop {} } + fn array_19() -> [(); { braced_braced_ident!{} }] { loop {} } + fn array_20() -> [(); unbraced_ident!{}] { loop {} } + fn array_21() -> [(); braced_ident!{}] { loop {} } + fn array_22() -> [(); { unbraced_ident!{} }] { loop {} } + fn array_23() -> [(); { braced_ident!{} }] { loop {} } + + // Arrays where the expanded result is a complex expr + fn array_24() -> [(); unbraced_unbraced_expr!{}] { loop {} } + fn array_25() -> [(); braced_unbraced_expr!{}] { loop {} } + fn array_26() -> [(); unbraced_braced_expr!{}] { loop {} } + fn array_27() -> [(); braced_braced_expr!{}] { loop {} } + fn array_28() -> [(); { unbraced_unbraced_expr!{} }] { loop {} } + fn array_29() -> [(); { braced_unbraced_expr!{} }] { loop {} } + fn array_30() -> [(); { unbraced_braced_expr!{} }] { loop {} } + fn array_31() -> [(); { braced_braced_expr!{} }] { loop {} } + fn array_32() -> [(); unbraced_expr!{}] { loop {} } + fn array_33() -> [(); braced_expr!{}] { loop {} } + fn array_34() -> [(); { unbraced_expr!{} }] { loop {} } + fn array_35() -> [(); { braced_expr!{} }] { loop {} } +} + +#[rustfmt::skip] +mod array_square_call { + // Arrays where the expanded result is a `Res::Err` + fn array_0() -> [(); unbraced_unbraced_ident![]] { loop {} } + fn array_1() -> [(); braced_unbraced_ident![]] { loop {} } + fn array_2() -> [(); unbraced_braced_ident![]] { loop {} } + fn array_3() -> [(); braced_braced_ident![]] { loop {} } + fn array_4() -> [(); { unbraced_unbraced_ident![] }] { loop {} } + fn array_5() -> [(); { braced_unbraced_ident![] }] { loop {} } + fn array_6() -> [(); { unbraced_braced_ident![] }] { loop {} } + fn array_7() -> [(); { braced_braced_ident![] }] { loop {} } + fn array_8() -> [(); unbraced_ident![]] { loop {} } + fn array_9() -> [(); braced_ident![]] { loop {} } + fn array_10() -> [(); { unbraced_ident![] }] { loop {} } + fn array_11() -> [(); { braced_ident![] }] { loop {} } + + // Arrays where the expanded result is a `Res::ConstParam` + fn array_12() -> [(); unbraced_unbraced_ident![]] { loop {} } + fn array_13() -> [(); braced_unbraced_ident![]] { loop {} } + fn array_14() -> [(); unbraced_braced_ident![]] { loop {} } + fn array_15() -> [(); braced_braced_ident![]] { loop {} } + fn array_16() -> [(); { unbraced_unbraced_ident![] }] { loop {} } + fn array_17() -> [(); { braced_unbraced_ident![] }] { loop {} } + fn array_18() -> [(); { unbraced_braced_ident![] }] { loop {} } + fn array_19() -> [(); { braced_braced_ident![] }] { loop {} } + fn array_20() -> [(); unbraced_ident![]] { loop {} } + fn array_21() -> [(); braced_ident![]] { loop {} } + fn array_22() -> [(); { unbraced_ident![] }] { loop {} } + fn array_23() -> [(); { braced_ident![] }] { loop {} } + + // Arrays where the expanded result is a complex expr + fn array_24() -> [(); unbraced_unbraced_expr![]] { loop {} } + fn array_25() -> [(); braced_unbraced_expr![]] { loop {} } + fn array_26() -> [(); unbraced_braced_expr![]] { loop {} } + fn array_27() -> [(); braced_braced_expr![]] { loop {} } + fn array_28() -> [(); { unbraced_unbraced_expr![] }] { loop {} } + fn array_29() -> [(); { braced_unbraced_expr![] }] { loop {} } + fn array_30() -> [(); { unbraced_braced_expr![] }] { loop {} } + fn array_31() -> [(); { braced_braced_expr![] }] { loop {} } + fn array_32() -> [(); unbraced_expr![]] { loop {} } + fn array_33() -> [(); braced_expr![]] { loop {} } + fn array_34() -> [(); { unbraced_expr![] }] { loop {} } + fn array_35() -> [(); { braced_expr![] }] { loop {} } +} + +struct Foo; + +#[rustfmt::skip] +mod adt_paren_call { + use super::Foo; + + // An ADT where the expanded result is a `Res::Err` + fn adt_0() -> Foo { loop {} } + fn adt_1() -> Foo { loop {} } + fn adt_2() -> Foo { loop {} } + fn adt_3() -> Foo { loop {} } + fn adt_4() -> Foo<{ unbraced_unbraced_ident!() }> { loop {} } + fn adt_5() -> Foo<{ braced_unbraced_ident!() }> { loop {} } + fn adt_6() -> Foo<{ unbraced_braced_ident!() }> { loop {} } + fn adt_7() -> Foo<{ braced_braced_ident!() }> { loop {} } + fn adt_8() -> Foo { loop {} } + fn adt_9() -> Foo { loop {} } + fn adt_10() -> Foo<{ unbraced_ident!() }> { loop {} } + fn adt_11() -> Foo<{ braced_ident!() }> { loop {} } + + // An ADT where the expanded result is a `Res::ConstParam` + fn adt_12() -> Foo { loop {} } + fn adt_13() -> Foo { loop {} } + fn adt_14() -> Foo { loop {} } + fn adt_15() -> Foo { loop {} } + fn adt_16() -> Foo<{ unbraced_unbraced_ident!() }> { loop {} } + fn adt_17() -> Foo<{ braced_unbraced_ident!() }> { loop {} } + fn adt_18() -> Foo<{ unbraced_braced_ident!() }> { loop {} } + fn adt_19() -> Foo<{ braced_braced_ident!() }> { loop {} } + fn adt_20() -> Foo { loop {} } + fn adt_21() -> Foo { loop {} } + fn adt_22() -> Foo<{ unbraced_ident!() }> { loop {} } + fn adt_23() -> Foo<{ braced_ident!() }> { loop {} } + + // An ADT where the expanded result is a complex expr + fn array_24() -> Foo { loop {} } + fn array_25() -> Foo { loop {} } + fn array_26() -> Foo { loop {} } + fn array_27() -> Foo { loop {} } + fn array_28() -> Foo<{ unbraced_unbraced_expr!() }> { loop {} } + fn array_29() -> Foo<{ braced_unbraced_expr!() }> { loop {} } + fn array_30() -> Foo<{ unbraced_braced_expr!() }> { loop {} } + fn array_31() -> Foo<{ braced_braced_expr!() }> { loop {} } + fn array_32() -> Foo { loop {} } + fn array_33() -> Foo { loop {} } + fn array_34() -> Foo<{ unbraced_expr!() }> { loop {} } + fn array_35() -> Foo<{ braced_expr!() }> { loop {} } +} + +#[rustfmt::skip] +mod adt_brace_call { + use super::Foo; + + // An ADT where the expanded result is a `Res::Err` + fn adt_0() -> Foo { loop {} } + fn adt_1() -> Foo { loop {} } + fn adt_2() -> Foo { loop {} } + fn adt_3() -> Foo { loop {} } + fn adt_4() -> Foo<{ unbraced_unbraced_ident!{} }> { loop {} } + fn adt_5() -> Foo<{ braced_unbraced_ident!{} }> { loop {} } + fn adt_6() -> Foo<{ unbraced_braced_ident!{} }> { loop {} } + fn adt_7() -> Foo<{ braced_braced_ident!{} }> { loop {} } + fn adt_8() -> Foo { loop {} } + fn adt_9() -> Foo { loop {} } + fn adt_10() -> Foo<{ unbraced_ident!{} }> { loop {} } + fn adt_11() -> Foo<{ braced_ident!{} }> { loop {} } + + // An ADT where the expanded result is a `Res::ConstParam` + fn adt_12() -> Foo { loop {} } + fn adt_13() -> Foo { loop {} } + fn adt_14() -> Foo { loop {} } + fn adt_15() -> Foo { loop {} } + fn adt_16() -> Foo<{ unbraced_unbraced_ident!{} }> { loop {} } + fn adt_17() -> Foo<{ braced_unbraced_ident!{} }> { loop {} } + fn adt_18() -> Foo<{ unbraced_braced_ident!{} }> { loop {} } + fn adt_19() -> Foo<{ braced_braced_ident!{} }> { loop {} } + fn adt_20() -> Foo { loop {} } + fn adt_21() -> Foo { loop {} } + fn adt_22() -> Foo<{ unbraced_ident!{} }> { loop {} } + fn adt_23() -> Foo<{ braced_ident!{} }> { loop {} } + + // An ADT where the expanded result is a complex expr + fn array_24() -> Foo { loop {} } + fn array_25() -> Foo { loop {} } + fn array_26() -> Foo { loop {} } + fn array_27() -> Foo { loop {} } + fn array_28() -> Foo<{ unbraced_unbraced_expr!{} }> { loop {} } + fn array_29() -> Foo<{ braced_unbraced_expr!{} }> { loop {} } + fn array_30() -> Foo<{ unbraced_braced_expr!{} }> { loop {} } + fn array_31() -> Foo<{ braced_braced_expr!{} }> { loop {} } + fn array_32() -> Foo { loop {} } + fn array_33() -> Foo { loop {} } + fn array_34() -> Foo<{ unbraced_expr!{} }> { loop {} } + fn array_35() -> Foo<{ braced_expr!{} }> { loop {} } +} + +#[rustfmt::skip] +mod adt_square_call { + use super::Foo; + + // An ADT where the expanded result is a `Res::Err` + fn adt_0() -> Foo { loop {} } + fn adt_1() -> Foo { loop {} } + fn adt_2() -> Foo { loop {} } + fn adt_3() -> Foo { loop {} } + fn adt_4() -> Foo<{ unbraced_unbraced_ident![] }> { loop {} } + fn adt_5() -> Foo<{ braced_unbraced_ident![] }> { loop {} } + fn adt_6() -> Foo<{ unbraced_braced_ident![] }> { loop {} } + fn adt_7() -> Foo<{ braced_braced_ident![] }> { loop {} } + fn adt_8() -> Foo { loop {} } + fn adt_9() -> Foo { loop {} } + fn adt_10() -> Foo<{ unbraced_ident![] }> { loop {} } + fn adt_11() -> Foo<{ braced_ident![] }> { loop {} } + + // An ADT where the expanded result is a `Res::ConstParam` + fn adt_12() -> Foo { loop {} } + fn adt_13() -> Foo { loop {} } + fn adt_14() -> Foo { loop {} } + fn adt_15() -> Foo { loop {} } + fn adt_16() -> Foo<{ unbraced_unbraced_ident![] }> { loop {} } + fn adt_17() -> Foo<{ braced_unbraced_ident![] }> { loop {} } + fn adt_18() -> Foo<{ unbraced_braced_ident![] }> { loop {} } + fn adt_19() -> Foo<{ braced_braced_ident![] }> { loop {} } + fn adt_20() -> Foo { loop {} } + fn adt_21() -> Foo { loop {} } + fn adt_22() -> Foo<{ unbraced_ident![] }> { loop {} } + fn adt_23() -> Foo<{ braced_ident![] }> { loop {} } + + // An ADT where the expanded result is a complex expr + fn array_24() -> Foo { loop {} } + fn array_25() -> Foo { loop {} } + fn array_26() -> Foo { loop {} } + fn array_27() -> Foo { loop {} } + fn array_28() -> Foo<{ unbraced_unbraced_expr![] }> { loop {} } + fn array_29() -> Foo<{ braced_unbraced_expr![] }> { loop {} } + fn array_30() -> Foo<{ unbraced_braced_expr![] }> { loop {} } + fn array_31() -> Foo<{ braced_braced_expr![] }> { loop {} } + fn array_32() -> Foo { loop {} } + fn array_33() -> Foo { loop {} } + fn array_34() -> Foo<{ unbraced_expr![] }> { loop {} } + fn array_35() -> Foo<{ braced_expr![] }> { loop {} } +} + +fn main() {} From 904e897d07b848d8e17e7addb40bc56fb58cd2ec Mon Sep 17 00:00:00 2001 From: Henry Jiang Date: Tue, 5 Nov 2024 14:21:08 -0500 Subject: [PATCH 037/214] fix missing use decl --- src/tools/run-make-support/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs index 368b98c9f0df..1b2fd6cbe061 100644 --- a/src/tools/run-make-support/src/lib.rs +++ b/src/tools/run-make-support/src/lib.rs @@ -77,7 +77,7 @@ pub use env::{env_var, env_var_os, set_current_dir}; pub use run::{cmd, run, run_fail, run_with_args}; /// Helpers for checking target information. -pub use targets::{is_darwin, is_msvc, is_windows, llvm_components_contain, target, uname, apple_os}; +pub use targets::{is_aix, is_darwin, is_msvc, is_windows, llvm_components_contain, target, uname, apple_os}; /// Helpers for building names of output artifacts that are potentially target-specific. pub use artifact_names::{ From 80df05b8b88a8c2f4371169d3d55528fe1063c91 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Wed, 6 Nov 2024 09:53:56 +0100 Subject: [PATCH 038/214] =?UTF-8?q?Add=20`pub=20fn=20all=5Fsupertraits(?= =?UTF-8?q?=E2=80=A6)`=20HIR-level=20method=20to=20`hir::Trait`=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 8c3b7a6d3cf5..00a46daa54e7 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2704,6 +2704,11 @@ impl Trait { db.trait_data(self.id).name.clone() } + pub fn all_supertraits(self, db: &dyn HirDatabase) -> Vec { + let traits = all_super_traits(db.upcast(), self.into()); + traits.iter().map(|tr| Trait::from(*tr)).collect() + } + pub fn items(self, db: &dyn HirDatabase) -> Vec { db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } From fa393dd9d8e0427e75b58ba3561cc1d58004bba1 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Wed, 6 Nov 2024 09:37:53 +0100 Subject: [PATCH 039/214] =?UTF-8?q?Refactor=20`hir::Trait`'s=20existing=20?= =?UTF-8?q?`items=5Fwith=5Fsupertraits(=E2=80=A6)`=20method=20based=20on?= =?UTF-8?q?=20new=20`all=5Fsupertraits(=E2=80=A6)`=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 00a46daa54e7..bfe9f3bd74ef 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2714,8 +2714,7 @@ impl Trait { } pub fn items_with_supertraits(self, db: &dyn HirDatabase) -> Vec { - let traits = all_super_traits(db.upcast(), self.into()); - traits.iter().flat_map(|tr| Trait::from(*tr).items(db)).collect() + self.all_supertraits(db).into_iter().flat_map(|tr| tr.items(db)).collect() } pub fn is_auto(self, db: &dyn HirDatabase) -> bool { From de30d7dc37239209827cdb7283e017863cdc0bd2 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Wed, 6 Nov 2024 09:52:49 +0100 Subject: [PATCH 040/214] Add `pub fn direct_super_traits(db, trait_id)` to `hir_ty` crate --- src/tools/rust-analyzer/crates/hir-ty/src/lib.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/utils.rs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 9c1d8bcf36f0..22e7b1d920f6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -98,7 +98,7 @@ pub use mapping::{ }; pub use method_resolution::check_orphan_rules; pub use traits::TraitEnvironment; -pub use utils::{all_super_traits, is_fn_unsafe_to_call}; +pub use utils::{all_super_traits, direct_super_traits, is_fn_unsafe_to_call}; pub use chalk_ir::{ cast::Cast, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 7429ce3c73e0..28bda1e10e58 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -43,6 +43,17 @@ pub(crate) fn fn_traits( .flat_map(|it| it.as_trait()) } +/// Returns an iterator over the direct super traits (including the trait itself). +pub fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { + let mut result = smallvec![trait_]; + direct_super_traits_cb(db, trait_, |tt| { + if !result.contains(&tt) { + result.push(tt); + } + }); + result +} + /// Returns an iterator over the whole super trait hierarchy (including the /// trait itself). pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { @@ -54,7 +65,7 @@ pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[Trai while let Some(&t) = result.get(i) { // yeah this is quadratic, but trait hierarchies should be flat // enough that this doesn't matter - direct_super_traits(db, t, |tt| { + direct_super_traits_cb(db, t, |tt| { if !result.contains(&tt) { result.push(tt); } @@ -153,7 +164,7 @@ impl Iterator for ClauseElaborator<'_> { } } -fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) { +fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) { let resolver = trait_.resolver(db); let generic_params = db.generic_params(trait_.into()); let trait_self = generic_params.trait_self_param(); From d5dec8ab035f50af3a34c30f90350ef3d3f2cae0 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Wed, 6 Nov 2024 09:53:11 +0100 Subject: [PATCH 041/214] =?UTF-8?q?Add=20`direct=5Fsupertraits(=E2=80=A6)`?= =?UTF-8?q?=20HIR-level=20method=20to=20`hir::Trait`=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index bfe9f3bd74ef..c9498b3aead7 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -68,7 +68,7 @@ use hir_ty::{ all_super_traits, autoderef, check_orphan_rules, consteval::{try_const_usize, unknown_const_as_generic, ConstExt}, diagnostics::BodyValidationDiagnostic, - error_lifetime, known_const_to_ast, + direct_super_traits, error_lifetime, known_const_to_ast, layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding}, method_resolution, mir::{interpret_mir, MutBorrowKind}, @@ -2704,6 +2704,11 @@ impl Trait { db.trait_data(self.id).name.clone() } + pub fn direct_supertraits(self, db: &dyn HirDatabase) -> Vec { + let traits = direct_super_traits(db.upcast(), self.into()); + traits.iter().map(|tr| Trait::from(*tr)).collect() + } + pub fn all_supertraits(self, db: &dyn HirDatabase) -> Vec { let traits = all_super_traits(db.upcast(), self.into()); traits.iter().map(|tr| Trait::from(*tr)).collect() From cd6ddcaf42ee3d66379bd205b82541957ed82e48 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Wed, 6 Nov 2024 15:00:59 -0800 Subject: [PATCH 042/214] editors/code: Change minimum VS Code from 1.78 to 1.83 It's been a year since we last bumped this (see #15904), and VS Code 1.83 is the first version that supports LSP 3.17.5 (via vscode-languageclient 9.0.1). https://code.visualstudio.com/updates/v1_83#_language-server-protocol --- .../editors/code/package-lock.json | 44 +++++++++---------- .../rust-analyzer/editors/code/package.json | 6 +-- .../rust-analyzer/editors/code/src/client.ts | 2 + 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 10b633511d3e..6027f813311c 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -13,12 +13,12 @@ "anser": "^2.1.1", "d3": "^7.8.5", "d3-graphviz": "^5.0.2", - "vscode-languageclient": "^8.1.0" + "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@tsconfig/strictest": "^2.0.1", "@types/node": "~16.11.7", - "@types/vscode": "~1.78.1", + "@types/vscode": "~1.83", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vscode/test-electron": "^2.3.8", @@ -32,7 +32,7 @@ "typescript": "^5.6.0" }, "engines": { - "vscode": "^1.78.0" + "vscode": "^1.83.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -880,9 +880,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.78.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.1.tgz", - "integrity": "sha512-wEA+54axejHu7DhcUfnFBan1IqFD1gBDxAFz8LoX06NbNDMRJv/T6OGthOs52yZccasKfN588EyffHWABkR0fg==", + "version": "1.83.3", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.83.3.tgz", + "integrity": "sha512-ZPp5+OQNYrCSFoT4jWOZKdcuXijj+JdN2BJNDhWH4pPbVL6PRQycG9NT8C4a94oul1tFMbkVbXXa9HasI7cLUg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -5059,24 +5059,24 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", - "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz", - "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", - "vscode-languageserver-protocol": "3.17.3" + "vscode-languageserver-protocol": "3.17.5" }, "engines": { - "vscode": "^1.67.0" + "vscode": "^1.82.0" } }, "node_modules/vscode-languageclient/node_modules/brace-expansion": { @@ -5099,18 +5099,18 @@ } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", - "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "dependencies": { - "vscode-jsonrpc": "8.1.0", - "vscode-languageserver-types": "3.17.3" + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" } }, "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/which": { "version": "2.0.2", diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index ccc8c0e38423..942d47c92837 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -27,7 +27,7 @@ } }, "engines": { - "vscode": "^1.78.0" + "vscode": "^1.83.0" }, "enabledApiProposals": [], "scripts": { @@ -49,12 +49,12 @@ "anser": "^2.1.1", "d3": "^7.8.5", "d3-graphviz": "^5.0.2", - "vscode-languageclient": "^8.1.0" + "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@tsconfig/strictest": "^2.0.1", "@types/node": "~16.11.7", - "@types/vscode": "~1.78.1", + "@types/vscode": "~1.83", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vscode/test-electron": "^2.3.8", diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts index bf58112916b6..eac7b849fdb9 100644 --- a/src/tools/rust-analyzer/editors/code/src/client.ts +++ b/src/tools/rust-analyzer/editors/code/src/client.ts @@ -350,6 +350,7 @@ class ExperimentalFeatures implements lc.StaticFeature { _documentSelector: lc.DocumentSelector | undefined, ): void {} dispose(): void {} + clear(): void {} } class OverrideFeatures implements lc.StaticFeature { @@ -369,6 +370,7 @@ class OverrideFeatures implements lc.StaticFeature { _documentSelector: lc.DocumentSelector | undefined, ): void {} dispose(): void {} + clear(): void {} } function isCodeActionWithoutEditsAndCommands(value: any): boolean { From aeffff8ecf84a0a49ee597fb41729f8de9972592 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 6 Nov 2024 12:09:15 -0800 Subject: [PATCH 043/214] optimize char::to_digit and assert radix is at least 2 approved by t-libs: https://github.com/rust-lang/libs-team/issues/475#issuecomment-2457858458 --- library/core/src/char/methods.rs | 44 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 02cc0f9d7704..453619913d97 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -301,7 +301,7 @@ impl char { /// /// # Panics /// - /// Panics if given a radix larger than 36. + /// Panics if given a radix smaller than 2 or larger than 36. /// /// # Examples /// @@ -319,6 +319,13 @@ impl char { /// // this panics /// '1'.is_digit(37); /// ``` + /// + /// Passing a small radix, causing a panic: + /// + /// ```should_panic + /// // this panics + /// '1'.is_digit(1); + /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_char_classify", issue = "132241")] #[inline] @@ -345,7 +352,7 @@ impl char { /// /// # Panics /// - /// Panics if given a radix larger than 36. + /// Panics if given a radix smaller than 2 or larger than 36. /// /// # Examples /// @@ -369,24 +376,35 @@ impl char { /// // this panics /// let _ = '1'.to_digit(37); /// ``` + /// Passing a small radix, causing a panic: + /// + /// ```should_panic + /// // this panics + /// let _ = '1'.to_digit(1); + /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_char_convert", since = "1.67.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] pub const fn to_digit(self, radix: u32) -> Option { - // If not a digit, a number greater than radix will be created. - let mut digit = (self as u32).wrapping_sub('0' as u32); - if radix > 10 { - assert!(radix <= 36, "to_digit: radix is too high (maximum 36)"); - if digit < 10 { - return Some(digit); - } - // Force the 6th bit to be set to ensure ascii is lower case. - digit = (self as u32 | 0b10_0000).wrapping_sub('a' as u32).saturating_add(10); - } + assert!( + radix >= 2 && radix <= 36, + "to_digit: invalid radix -- radix must be in the range 2 to 36 inclusive" + ); + // check radix to remove letter handling code when radix is a known constant + let value = if self > '9' && radix > 10 { + // convert ASCII letters to lowercase + let lower = self as u32 | 0x20; + // convert an ASCII letter to the corresponding value, + // non-letters convert to values > 36 + lower.wrapping_sub('a' as u32) as u64 + 10 + } else { + // convert digit to value, non-digits wrap to values > 36 + (self as u32).wrapping_sub('0' as u32) as u64 + }; // FIXME(const-hack): once then_some is const fn, use it here - if digit < radix { Some(digit) } else { None } + if value < radix as u64 { Some(value as u32) } else { None } } /// Returns an iterator that yields the hexadecimal Unicode escape of a From cf9cec3d843b1b6daae76e99027d180b11530bff Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 7 Nov 2024 19:39:18 +0800 Subject: [PATCH 044/214] Only copy, rename and link `llvm-objcopy` if llvm tools are enabled Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> --- src/bootstrap/src/core/build_steps/compile.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3394f2a84a04..24be705f4819 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1976,9 +1976,13 @@ impl Step for Assemble { } } - { - // `llvm-strip` is used by rustc, which is actually just a symlink to `llvm-objcopy`, - // so copy and rename `llvm-objcopy`. + if builder.config.llvm_enabled(target_compiler.host) && builder.config.llvm_tools_enabled { + // `llvm-strip` is used by rustc, which is actually just a symlink to `llvm-objcopy`, so + // copy and rename `llvm-objcopy`. + // + // But only do so if llvm-tools are enabled, as bootstrap compiler might not contain any + // LLVM tools, e.g. for cg_clif. + // See . let src_exe = exe("llvm-objcopy", target_compiler.host); let dst_exe = exe("rust-objcopy", target_compiler.host); builder.copy_link(&libdir_bin.join(src_exe), &libdir_bin.join(dst_exe)); From 902a2c5060a84c665474149bf3d9f29963690687 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Thu, 7 Nov 2024 23:00:29 +0900 Subject: [PATCH 045/214] minor: Rename `dyn compatible` to `dyn-compatible` --- src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs | 3 ++- .../rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs | 2 +- .../crates/hir-ty/src/dyn_compatibility/tests.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/hover/render.rs | 6 +++--- src/tools/rust-analyzer/crates/ide/src/hover/tests.rs | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index 05cd7bd37b4b..53795c0b600b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -381,8 +381,9 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { TyKind::Error.intern(Interner) } + // object safety was renamed to dyn-compatibility but still remains here in chalk. + // This will be removed since we are going to migrate to next-gen trait solver. fn is_object_safe(&self, trait_id: chalk_ir::TraitId) -> bool { - // FIXME: When cargo is updated, change to dyn_compatibility let trait_ = from_chalk_trait_id(trait_id); crate::dyn_compatibility::dyn_compatibility(self.db, trait_).is_none() } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs index 3d21785a70a3..fadf8aca9982 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs @@ -472,7 +472,7 @@ fn receiver_is_dispatchable( return false; }; - // `self: Self` can't be dispatched on, but this is already considered dyn compatible + // `self: Self` can't be dispatched on, but this is already considered dyn-compatible // See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437 if sig .skip_binders() diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs index 3f3e68eeb1c2..8a56bd28b598 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs @@ -66,7 +66,7 @@ fn check_dyn_compatibility<'a>( }); ControlFlow::Continue(()) }); - assert_eq!(osvs, expected, "Dyn Compatibility violations for `{name}` do not match;"); + assert_eq!(osvs, expected, "dyn-compatibility violations for `{name}` do not match;"); } let remains: Vec<_> = expected.keys().collect(); diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index 51a77283459a..e9857e51225e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -1003,10 +1003,10 @@ fn render_dyn_compatibility( safety: Option, ) { let Some(osv) = safety else { - buf.push_str("Is Dyn compatible"); + buf.push_str("Is dyn-compatible"); return; }; - buf.push_str("Is not Dyn compatible due to "); + buf.push_str("Is not dyn-compatible due to "); match osv { DynCompatibilityViolation::SizedSelf => { buf.push_str("having a `Self: Sized` bound"); @@ -1049,7 +1049,7 @@ fn render_dyn_compatibility( } DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait) => { let name = hir::Trait::from(super_trait).name(db); - format_to!(buf, "having a dyn incompatible supertrait `{}`", name.as_str()); + format_to!(buf, "having a dyn-incompatible supertrait `{}`", name.as_str()); } } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 1fad3d6bd6f1..448bcd22d61e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -9371,7 +9371,7 @@ trait Compat$0 {} --- - Is Dyn compatible + Is dyn-compatible "#]], ); check( @@ -9393,7 +9393,7 @@ trait UnCompat$0 { --- - Is not Dyn compatible due to having a method `f` that is not dispatchable due to missing a receiver + Is not dyn-compatible due to having a method `f` that is not dispatchable due to missing a receiver "#]], ); check( From 01726389fb7f28c2ba253f06cdb087f8e422fd41 Mon Sep 17 00:00:00 2001 From: Sam Estep Date: Thu, 7 Nov 2024 11:49:22 -0500 Subject: [PATCH 046/214] Delete design label from list --- src/tools/rust-analyzer/docs/dev/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tools/rust-analyzer/docs/dev/README.md b/src/tools/rust-analyzer/docs/dev/README.md index 12e6d829a089..0d47ff09dcc2 100644 --- a/src/tools/rust-analyzer/docs/dev/README.md +++ b/src/tools/rust-analyzer/docs/dev/README.md @@ -47,10 +47,6 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer Each triaged issue should have one of these labels. * [fun](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun) is for cool, but probably hard stuff. -* [Design](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%Design) - is for moderate/large scale architecture discussion. - Also a kind of fun. - These issues should generally include a link to a Zulip discussion thread. # Code Style & Review Process From 6ced8c33c058fa1df65a363abcdc5e2c5828fa66 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 7 Nov 2024 22:31:20 +0100 Subject: [PATCH 047/214] Merge commit 'f712eb5cdccd121d0569af12f20e6a0fabe4364d' into clippy-subtree-update --- .github/workflows/clippy_dev.yml | 50 +- .../{clippy_bors.yml => clippy_mq.yml} | 51 +- .../workflows/{clippy.yml => clippy_pr.yml} | 40 +- .github/workflows/deploy.yml | 2 +- .github/workflows/remark.yml | 46 +- CHANGELOG.md | 7 + CONTRIBUTING.md | 16 +- Cargo.toml | 2 +- README.md | 3 +- book/src/README.md | 3 +- book/src/development/README.md | 1 - .../development/common_tools_writing_lints.md | 4 +- book/src/development/method_checking.md | 15 +- book/src/lint_configuration.md | 33 +- clippy_config/src/conf.rs | 46 +- clippy_config/src/lib.rs | 1 + clippy_config/src/msrvs.rs | 13 +- clippy_config/src/types.rs | 301 ++++++++++ clippy_dev/src/serve.rs | 8 +- clippy_dev/src/update_lints.rs | 20 +- .../src/arbitrary_source_item_ordering.rs | 531 ++++++++++++++++++ clippy_lints/src/attrs/mod.rs | 2 +- clippy_lints/src/attrs/useless_attribute.rs | 5 +- clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/booleans.rs | 15 + clippy_lints/src/borrow_deref_ref.rs | 4 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 2 +- clippy_lints/src/casts/unnecessary_cast.rs | 3 +- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/cognitive_complexity.rs | 2 +- clippy_lints/src/copies.rs | 16 +- clippy_lints/src/copy_iterator.rs | 2 +- clippy_lints/src/ctfe.rs | 1 - clippy_lints/src/declare_clippy_lint.rs | 49 +- clippy_lints/src/declared_lints.rs | 6 + clippy_lints/src/derivable_impls.rs | 2 +- clippy_lints/src/derive.rs | 40 +- clippy_lints/src/disallowed_macros.rs | 23 +- clippy_lints/src/disallowed_script_idents.rs | 2 +- clippy_lints/src/doc/mod.rs | 8 +- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/eta_reduction.rs | 18 +- clippy_lints/src/explicit_write.rs | 2 +- clippy_lints/src/from_over_into.rs | 29 +- clippy_lints/src/from_raw_with_void_ptr.rs | 2 +- clippy_lints/src/functions/mod.rs | 2 +- clippy_lints/src/functions/ref_option.rs | 6 +- clippy_lints/src/implicit_hasher.rs | 4 +- clippy_lints/src/infinite_iter.rs | 6 +- clippy_lints/src/iter_without_into_iter.rs | 4 +- clippy_lints/src/large_const_arrays.rs | 4 +- clippy_lints/src/large_include_file.rs | 63 ++- clippy_lints/src/len_zero.rs | 2 +- clippy_lints/src/lib.rs | 12 +- clippy_lints/src/loops/explicit_iter_loop.rs | 6 +- clippy_lints/src/loops/infinite_loop.rs | 59 +- .../src/loops/while_immutable_condition.rs | 15 +- .../src/loops/while_let_on_iterator.rs | 60 +- clippy_lints/src/manual_async_fn.rs | 23 +- clippy_lints/src/manual_bits.rs | 5 +- clippy_lints/src/manual_hash_one.rs | 4 +- clippy_lints/src/manual_is_ascii_check.rs | 2 +- .../src/matches/match_like_matches.rs | 2 +- .../src/matches/match_str_case_mismatch.rs | 30 +- clippy_lints/src/matches/redundant_guards.rs | 12 +- .../src/matches/redundant_pattern_match.rs | 15 +- clippy_lints/src/matches/single_match.rs | 5 +- clippy_lints/src/methods/filter_map.rs | 8 +- clippy_lints/src/methods/is_empty.rs | 13 +- .../src/methods/iter_out_of_bounds.rs | 5 +- .../src/methods/map_all_any_identity.rs | 43 ++ .../map_with_unused_argument_over_ranges.rs | 134 +++++ clippy_lints/src/methods/mod.rs | 134 ++++- clippy_lints/src/methods/needless_as_bytes.rs | 28 + clippy_lints/src/methods/needless_collect.rs | 2 +- .../src/methods/read_line_without_trim.rs | 4 +- .../src/methods/unnecessary_filter_map.rs | 33 +- clippy_lints/src/minmax.rs | 4 +- clippy_lints/src/missing_doc.rs | 25 +- clippy_lints/src/missing_fields_in_debug.rs | 2 +- clippy_lints/src/needless_continue.rs | 5 +- clippy_lints/src/needless_late_init.rs | 4 +- clippy_lints/src/needless_maybe_sized.rs | 2 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 2 +- clippy_lints/src/no_effect.rs | 8 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 13 +- .../src/non_octal_unix_permissions.rs | 4 +- clippy_lints/src/operators/float_cmp.rs | 2 +- clippy_lints/src/partialeq_ne_impl.rs | 2 +- clippy_lints/src/ptr_offset_with_cast.rs | 2 +- clippy_lints/src/question_mark.rs | 6 +- clippy_lints/src/ref_option_ref.rs | 2 +- clippy_lints/src/returns.rs | 2 +- clippy_lints/src/semicolon_block.rs | 27 +- clippy_lints/src/serde_api.rs | 2 +- .../src/single_component_path_imports.rs | 2 +- .../src/slow_vector_initialization.rs | 6 +- clippy_lints/src/strings.rs | 6 +- .../src/suspicious_operation_groupings.rs | 28 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/trait_bounds.rs | 10 +- clippy_lints/src/transmute/eager_transmute.rs | 4 +- .../src/undocumented_unsafe_blocks.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/unit_types/unit_arg.rs | 2 +- clippy_lints/src/unused_io_amount.rs | 28 +- clippy_lints/src/utils/attr_collector.rs | 40 ++ clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/utils/internal_lints.rs | 1 + .../utils/internal_lints/collapsible_calls.rs | 2 +- .../internal_lints/lint_without_lint_pass.rs | 14 +- .../utils/internal_lints/msrv_attr_impl.rs | 2 +- .../internal_lints/slow_symbol_comparisons.rs | 69 +++ clippy_lints/src/utils/mod.rs | 2 + clippy_utils/src/check_proc_macro.rs | 28 +- clippy_utils/src/consts.rs | 6 + clippy_utils/src/diagnostics.rs | 63 ++- clippy_utils/src/higher.rs | 8 +- clippy_utils/src/hir_utils.rs | 15 +- clippy_utils/src/lib.rs | 30 +- clippy_utils/src/ty.rs | 19 +- clippy_utils/src/ty/type_certainty/mod.rs | 6 +- clippy_utils/src/usage.rs | 18 +- clippy_utils/src/visitors.rs | 83 ++- rust-toolchain | 2 +- tests/compile-test.rs | 3 +- tests/ui-internal/lint_without_lint_pass.rs | 5 +- .../ui-internal/slow_symbol_comparisons.fixed | 24 + tests/ui-internal/slow_symbol_comparisons.rs | 24 + .../slow_symbol_comparisons.stderr | 23 + .../ui-internal/unnecessary_symbol_str.fixed | 1 + tests/ui-internal/unnecessary_symbol_str.rs | 1 + .../ui-internal/unnecessary_symbol_str.stderr | 10 +- .../bad_conf_1/clippy.toml | 1 + .../bad_conf_2/clippy.toml | 1 + .../bad_conf_3/clippy.toml | 1 + .../default/clippy.toml | 0 .../default_exp/clippy.toml | 12 + .../only_enum/clippy.toml | 1 + .../only_impl/clippy.toml | 1 + .../only_trait/clippy.toml | 1 + .../ordering_good.bad_conf_1.stderr | 8 + .../ordering_good.bad_conf_2.stderr | 8 + .../ordering_good.bad_conf_3.stderr | 8 + .../ordering_good.rs | 186 ++++++ .../ordering_good_var_1.rs | 174 ++++++ .../ordering_mixed.default.stderr | 226 ++++++++ .../ordering_mixed.rs | 185 ++++++ .../ordering_mixed_var_1.rs | 174 ++++++ .../ordering_mixed_var_1.var_1.stderr | 100 ++++ .../ordering_only_enum.only_enum.stderr | 16 + .../ordering_only_enum.rs | 43 ++ .../ordering_only_impl.only_impl.stderr | 40 ++ .../ordering_only_impl.rs | 67 +++ .../ordering_only_trait.only_trait.stderr | 40 ++ .../ordering_only_trait.rs | 48 ++ .../var_1/clippy.toml | 1 + .../disallowed_macros.stderr | 62 +- .../large_include_file/large_include_file.rs | 7 +- .../large_include_file.stderr | 12 +- tests/ui-toml/private-doc-errors/doc_lints.rs | 6 + .../toml_unknown_key/conf_unknown_key.stderr | 9 + tests/ui/allow_attributes.fixed | 7 + tests/ui/allow_attributes.rs | 7 + tests/ui/allow_attributes.stderr | 6 +- tests/{ui-internal => ui}/author.rs | 2 - tests/{ui-internal => ui}/author.stdout | 0 tests/{ui-internal => ui}/author/blocks.rs | 0 .../{ui-internal => ui}/author/blocks.stdout | 0 tests/{ui-internal => ui}/author/call.rs | 0 tests/{ui-internal => ui}/author/call.stdout | 0 tests/{ui-internal => ui}/author/if.rs | 0 tests/{ui-internal => ui}/author/if.stdout | 0 .../{ui-internal => ui}/author/issue_3849.rs | 0 .../author/issue_3849.stdout | 0 tests/{ui-internal => ui}/author/loop.rs | 0 tests/{ui-internal => ui}/author/loop.stdout | 0 .../author/macro_in_closure.rs | 0 .../author/macro_in_closure.stdout | 0 .../author/macro_in_loop.rs | 0 .../author/macro_in_loop.stdout | 0 tests/{ui-internal => ui}/author/matches.rs | 0 .../{ui-internal => ui}/author/matches.stdout | 0 tests/{ui-internal => ui}/author/repeat.rs | 0 .../{ui-internal => ui}/author/repeat.stdout | 0 tests/{ui-internal => ui}/author/struct.rs | 0 .../{ui-internal => ui}/author/struct.stdout | 0 tests/ui/auxiliary/proc_macro_derive.rs | 50 +- tests/ui/borrow_deref_ref.fixed | 6 + tests/ui/borrow_deref_ref.rs | 6 + tests/ui/const_is_empty.rs | 14 + tests/ui/const_is_empty.stderr | 8 +- tests/ui/doc/doc-fixable.fixed | 3 +- tests/ui/doc/doc-fixable.rs | 3 +- tests/ui/doc/doc-fixable.stderr | 44 +- tests/ui/infinite_loops.rs | 20 + tests/ui/infinite_loops.stderr | 42 +- tests/ui/iter_without_into_iter.stderr | 16 +- tests/ui/large_const_arrays.fixed | 2 + tests/ui/large_const_arrays.rs | 2 + tests/ui/large_const_arrays.stderr | 22 +- tests/ui/manual_bits.fixed | 11 + tests/ui/manual_bits.rs | 11 + tests/ui/map_all_any_identity.fixed | 21 + tests/ui/map_all_any_identity.rs | 21 + tests/ui/map_all_any_identity.stderr | 26 + ...map_with_unused_argument_over_ranges.fixed | 73 +++ .../map_with_unused_argument_over_ranges.rs | 73 +++ ...ap_with_unused_argument_over_ranges.stderr | 223 ++++++++ .../missing_const_for_fn/could_be_const.fixed | 1 - .../ui/missing_const_for_fn/could_be_const.rs | 1 - .../could_be_const.stderr | 52 +- tests/ui/missing_doc.rs | 8 + tests/ui/missing_doc.stderr | 11 +- tests/ui/needless_as_bytes.fixed | 50 ++ tests/ui/needless_as_bytes.rs | 50 ++ tests/ui/needless_as_bytes.stderr | 29 + tests/ui/needless_continue.rs | 17 + tests/ui/needless_continue.stderr | 10 +- tests/ui/no_lints.rs | 3 - tests/ui/no_mangle_with_rust_abi.rs | 29 +- tests/ui/no_mangle_with_rust_abi.stderr | 27 +- tests/ui/no_mangle_with_rust_abi_2021.rs | 57 ++ tests/ui/no_mangle_with_rust_abi_2021.stderr | 83 +++ tests/ui/nonminimal_bool.stderr | 8 +- tests/ui/nonminimal_bool_methods.fixed | 30 +- tests/ui/nonminimal_bool_methods.rs | 24 + tests/ui/nonminimal_bool_methods.stderr | 86 ++- tests/ui/rename.rs | 2 +- tests/ui/rename.stderr | 14 +- tests/ui/repeat_vec_with_capacity.fixed | 1 + tests/ui/repeat_vec_with_capacity.rs | 1 + tests/ui/repeat_vec_with_capacity.stderr | 6 +- tests/ui/semicolon_outside_block.fixed | 8 + tests/ui/semicolon_outside_block.rs | 8 + tests/ui/semicolon_outside_block.stderr | 30 +- tests/ui/suspicious_map.rs | 1 + tests/ui/suspicious_map.stderr | 4 +- tests/ui/unnecessary_filter_map.rs | 13 +- tests/ui/unnecessary_filter_map.stderr | 53 +- tests/ui/unnecessary_find_map.rs | 9 +- tests/ui/unnecessary_find_map.stderr | 30 +- tests/ui/unused_io_amount.rs | 14 + triagebot.toml | 2 - util/gh-pages/index_template.html | 43 +- util/gh-pages/script.js | 22 +- util/gh-pages/style.css | 39 +- util/gh-pages/theme.js | 4 + 248 files changed, 5023 insertions(+), 900 deletions(-) rename .github/workflows/{clippy_bors.yml => clippy_mq.yml} (82%) rename .github/workflows/{clippy.yml => clippy_pr.yml} (57%) create mode 100644 clippy_lints/src/arbitrary_source_item_ordering.rs create mode 100644 clippy_lints/src/methods/map_all_any_identity.rs create mode 100644 clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs create mode 100644 clippy_lints/src/methods/needless_as_bytes.rs create mode 100644 clippy_lints/src/utils/attr_collector.rs create mode 100644 clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs create mode 100644 tests/ui-internal/slow_symbol_comparisons.fixed create mode 100644 tests/ui-internal/slow_symbol_comparisons.rs create mode 100644 tests/ui-internal/slow_symbol_comparisons.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml rename tests/{ui-internal => ui}/author.rs (72%) rename tests/{ui-internal => ui}/author.stdout (100%) rename tests/{ui-internal => ui}/author/blocks.rs (100%) rename tests/{ui-internal => ui}/author/blocks.stdout (100%) rename tests/{ui-internal => ui}/author/call.rs (100%) rename tests/{ui-internal => ui}/author/call.stdout (100%) rename tests/{ui-internal => ui}/author/if.rs (100%) rename tests/{ui-internal => ui}/author/if.stdout (100%) rename tests/{ui-internal => ui}/author/issue_3849.rs (100%) rename tests/{ui-internal => ui}/author/issue_3849.stdout (100%) rename tests/{ui-internal => ui}/author/loop.rs (100%) rename tests/{ui-internal => ui}/author/loop.stdout (100%) rename tests/{ui-internal => ui}/author/macro_in_closure.rs (100%) rename tests/{ui-internal => ui}/author/macro_in_closure.stdout (100%) rename tests/{ui-internal => ui}/author/macro_in_loop.rs (100%) rename tests/{ui-internal => ui}/author/macro_in_loop.stdout (100%) rename tests/{ui-internal => ui}/author/matches.rs (100%) rename tests/{ui-internal => ui}/author/matches.stdout (100%) rename tests/{ui-internal => ui}/author/repeat.rs (100%) rename tests/{ui-internal => ui}/author/repeat.stdout (100%) rename tests/{ui-internal => ui}/author/struct.rs (100%) rename tests/{ui-internal => ui}/author/struct.stdout (100%) create mode 100644 tests/ui/map_all_any_identity.fixed create mode 100644 tests/ui/map_all_any_identity.rs create mode 100644 tests/ui/map_all_any_identity.stderr create mode 100644 tests/ui/map_with_unused_argument_over_ranges.fixed create mode 100644 tests/ui/map_with_unused_argument_over_ranges.rs create mode 100644 tests/ui/map_with_unused_argument_over_ranges.stderr create mode 100644 tests/ui/needless_as_bytes.fixed create mode 100644 tests/ui/needless_as_bytes.rs create mode 100644 tests/ui/needless_as_bytes.stderr delete mode 100644 tests/ui/no_lints.rs create mode 100644 tests/ui/no_mangle_with_rust_abi_2021.rs create mode 100644 tests/ui/no_mangle_with_rust_abi_2021.stderr diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index cf0a8bde202a..bcb3193ad670 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -1,17 +1,8 @@ name: Clippy Dev Test on: - push: - branches: - - auto - - try + merge_group: pull_request: - # Only run on paths, that get checked by the clippy_dev tool - paths: - - 'CHANGELOG.md' - - 'README.md' - - '**.stderr' - - '**.rs' env: RUST_BACKTRACE: 1 @@ -47,28 +38,21 @@ jobs: cargo check git reset --hard HEAD - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors dev test finished - if: github.event.pusher.name == 'bors' && success() + conclusion_dev: + needs: [ clippy_dev ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [clippy_dev] - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors dev test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) - runs-on: ubuntu-latest - needs: [clippy_dev] - - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_mq.yml similarity index 82% rename from .github/workflows/clippy_bors.yml rename to .github/workflows/clippy_mq.yml index 026771e6fcf4..496220480508 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_mq.yml @@ -1,10 +1,7 @@ -name: Clippy Test (bors) +name: Clippy Test (merge queue) on: - push: - branches: - - auto - - try + merge_group: env: RUST_BACKTRACE: 1 @@ -13,11 +10,6 @@ env: 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. - group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" - cancel-in-progress: true - defaults: run: shell: bash @@ -218,28 +210,21 @@ jobs: env: INTEGRATION: ${{ matrix.integration }} - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors test finished - if: github.event.pusher.name == 'bors' && success() + conclusion: + needs: [ changelog, base, metadata_collection, integration_build, integration ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [changelog, base, metadata_collection, integration_build, integration] - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) - runs-on: ubuntu-latest - needs: [changelog, base, metadata_collection, integration_build, integration] - - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy_pr.yml similarity index 57% rename from .github/workflows/clippy.yml rename to .github/workflows/clippy_pr.yml index 0a0538490cce..2e5b5bd41dfb 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy_pr.yml @@ -1,24 +1,7 @@ name: Clippy Test on: - push: - # Ignore bors branches, since they are covered by `clippy_bors.yml` - branches-ignore: - - auto - - try - # Don't run Clippy tests, when only text files were modified - paths-ignore: - - 'COPYRIGHT' - - 'LICENSE-*' - - '**.md' - - '**.txt' pull_request: - # Don't run Clippy tests, when only text files were modified - paths-ignore: - - 'COPYRIGHT' - - 'LICENSE-*' - - '**.md' - - '**.txt' env: RUST_BACKTRACE: 1 @@ -35,7 +18,7 @@ concurrency: jobs: base: - # NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml + # NOTE: If you modify this job, make sure you copy the changes to clippy_mq.yml runs-on: ubuntu-latest steps: @@ -73,3 +56,24 @@ jobs: run: .github/driver.sh env: OS: ${{ runner.os }} + + # We need to have the "conclusion" job also on PR CI, to make it possible + # to add PRs to a merge queue. + conclusion: + needs: [ base ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + steps: + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94f494b65c47..32dc251c836f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -52,7 +52,7 @@ jobs: run: cargo generate-lockfile - name: Cache - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index a1b011dc32d8..0d402fe70641 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -1,13 +1,8 @@ name: Remark on: - push: - branches: - - auto - - try + merge_group: pull_request: - paths: - - '**.md' jobs: remark: @@ -45,28 +40,21 @@ jobs: - name: Build mdbook run: mdbook build book - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors remark test finished - if: github.event.pusher.name == 'bors' && success() + conclusion_remark: + needs: [ remark ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [remark] - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors remark test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) - runs-on: ubuntu-latest - needs: [remark] - - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdbc91db939..161fa630ed47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5331,6 +5331,7 @@ Released 2018-09-13 [`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant +[`arbitrary_source_item_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering [`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync [`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions @@ -5689,6 +5690,7 @@ Released 2018-09-13 [`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default [`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some [`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names +[`map_all_any_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity [`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone [`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit [`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry @@ -5696,6 +5698,7 @@ Released 2018-09-13 [`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten [`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity [`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or +[`map_with_unused_argument_over_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges [`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref [`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool [`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro @@ -5761,6 +5764,7 @@ Released 2018-09-13 [`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer [`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount [`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type +[`needless_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes [`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool [`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool [`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign @@ -6205,12 +6209,14 @@ Released 2018-09-13 [`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds [`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold [`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items +[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings [`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv [`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit [`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior [`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline [`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline [`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold +[`source-item-ordering`]: https://doc.rust-lang.org/clippy/lint_configuration.html#source-item-ordering [`stack-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#stack-size-threshold [`standard-macro-braces`]: https://doc.rust-lang.org/clippy/lint_configuration.html#standard-macro-braces [`struct-field-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#struct-field-name-threshold @@ -6218,6 +6224,7 @@ Released 2018-09-13 [`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack [`too-many-arguments-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-arguments-threshold [`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold +[`trait-assoc-item-kinds-order`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trait-assoc-item-kinds-order [`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit [`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold [`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1a59238c826..1f6c918fc6cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,6 @@ All contributors are expected to follow the [Rust Code of Conduct]. - [Rust Analyzer](#rust-analyzer) - [How Clippy works](#how-clippy-works) - [Issue and PR triage](#issue-and-pr-triage) - - [Bors and Homu](#bors-and-homu) - [Contributions](#contributions) - [License](#license) @@ -213,16 +212,6 @@ We have prioritization labels and a sync-blocker label, which are described belo Or rather: before the sync this should be addressed, e.g. by removing a lint again, so it doesn't hit beta/stable. -## Bors and Homu - -We use a bot powered by [Homu][homu] to help automate testing and landing of pull -requests in Clippy. The bot's username is @bors. - -You can find the Clippy bors queue [here][homu_queue]. - -If you have @bors permissions, you can find an overview of the available -commands [here][homu_instructions]. - [triage]: https://forge.rust-lang.org/release/triage-procedure.html [l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash [l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug @@ -230,9 +219,6 @@ commands [here][homu_instructions]. [p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium [p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high [l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker -[homu]: https://github.com/rust-lang/homu -[homu_instructions]: https://bors.rust-lang.org/ -[homu_queue]: https://bors.rust-lang.org/queue/clippy ## Contributions @@ -244,7 +230,7 @@ All PRs should include a `changelog` entry with a short comment explaining the c "what do you believe is important from an outsider's perspective?" Often, PRs are only related to a single property of a lint, and then it's good to mention that one. Otherwise, it's better to include too much detail than too little. -Clippy's [changelog] is created from these comments. Every release, someone gets all commits from bors with a +Clippy's [changelog] is created from these comments. Every release, someone gets all merge commits with a `changelog: XYZ` entry and combines them into the changelog. This is a manual process. Examples: diff --git a/Cargo.toml b/Cargo.toml index 1f7784fc489f..50a2afbfc072 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" -pulldown-cmark = "0.11" +pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } rinja = { version = "0.3", default-features = false, features = ["config"] } # UI test dependencies diff --git a/README.md b/README.md index ec76a6dfb08e..cb3a22d4288f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Clippy -[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. diff --git a/book/src/README.md b/book/src/README.md index 7bdfb97c3acf..5d2c3972b060 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,12 +1,11 @@ # Clippy -[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/book/src/development/README.md b/book/src/development/README.md index 8f09f66f5958..b33cdc00ead6 100644 --- a/book/src/development/README.md +++ b/book/src/development/README.md @@ -53,7 +53,6 @@ book](../lints.md). > - IDE setup > - High level overview on how Clippy works > - Triage procedure -> - Bors and Homu [ast]: https://rustc-dev-guide.rust-lang.org/syntax-intro.html [hir]: https://rustc-dev-guide.rust-lang.org/hir.html diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index 09171d86a209..77910917963e 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint { // Check our expr is calling a method if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind // Check the name of this method is `some_method` - && path.ident.name == sym!(some_method) + && path.ident.name.as_str() == "some_method" // Optionally, check the type of the self argument. // - See "Checking for a specific type" { @@ -167,7 +167,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // Check if item is a method/function if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // Check the method is named `some_method` - && impl_item.ident.name == sym!(some_method) + && impl_item.ident.name.as_str() == "some_method" // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go further and even check if its return type is `String` diff --git a/book/src/development/method_checking.md b/book/src/development/method_checking.md index 56d1be37519e..9c5d4b516db2 100644 --- a/book/src/development/method_checking.md +++ b/book/src/development/method_checking.md @@ -3,8 +3,8 @@ In some scenarios we might want to check for methods when developing a lint. There are two kinds of questions that we might be curious about: -- Invocation: Does an expression call a specific method? -- Definition: Does an `impl` define a method? +- Invocation: Does an expression call a specific method? +- Definition: Does an `impl` define a method? ## Checking if an `expr` is calling a specific method @@ -23,7 +23,7 @@ impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint { // Check our expr is calling a method with pattern matching if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind // Check if the name of this method is `our_fancy_method` - && path.ident.name == sym!(our_fancy_method) + && path.ident.name.as_str() == "our_fancy_method" // We can check the type of the self argument whenever necessary. // (It's necessary if we want to check that method is specifically belonging to a specific trait, // for example, a `map` method could belong to user-defined trait instead of to `Iterator`) @@ -41,10 +41,6 @@ information on the pattern matching. As mentioned in [Define Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern matching with `MethodCall` in case the reader wishes to explore more. -Additionally, we use the [`clippy_utils::sym!`][sym] macro to conveniently -convert an input `our_fancy_method` into a `Symbol` and compare that symbol to -the [`Ident`]'s name in the [`PathSegment`] in the [`MethodCall`]. - ## Checking if a `impl` block implements a method While sometimes we want to check whether a method is being called or not, other @@ -71,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // Check if item is a method/function if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // Check the method is named `our_fancy_method` - && impl_item.ident.name == sym!(our_fancy_method) + && impl_item.ident.name.as_str() == "our_fancy_method" // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go even further and even check if its return type is `String` @@ -85,9 +81,6 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { [`check_impl_item`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_impl_item [`ExprKind`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html -[`Ident`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/struct.Ident.html [`ImplItem`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_hir/hir/struct.ImplItem.html [`LateLintPass`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html [`MethodCall`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.MethodCall -[`PathSegment`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/struct.PathSegment.html -[sym]: https://doc.rust-lang.org/stable/nightly-rustc/clippy_utils/macro.sym.html diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 43b551ae2161..670b5cbef823 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -456,7 +456,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "AccessKit", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** @@ -666,6 +666,16 @@ crate. For example, `pub(crate)` items. * [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items) +## `module-item-order-groupings` +The named groupings of different source item kinds within modules. + +**Default Value:** `[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `msrv` The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` @@ -710,6 +720,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold) * [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) * [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or) +* [`map_with_unused_argument_over_ranges`](https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges) * [`match_like_matches_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro) * [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default) * [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn) @@ -783,6 +794,16 @@ The maximum number of single char bindings a scope may have * [`many_single_char_names`](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names) +## `source-item-ordering` +Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`. + +**Default Value:** `["enum", "impl", "module", "struct", "trait"]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `stack-size-threshold` The maximum allowed stack size for functions in bytes @@ -862,6 +883,16 @@ The maximum number of lines a function or method can have * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines) +## `trait-assoc-item-kinds-order` +The order of associated items in traits. + +**Default Value:** `["const", "type", "fn"]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `trivial-copy-size-limit` The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. By default there is no limit diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 4757c0b1339a..600d5b6e2c8d 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -1,6 +1,10 @@ use crate::ClippyConfiguration; use crate::msrvs::Msrv; -use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename}; +use crate::types::{ + DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, + SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, + SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, +}; use rustc_errors::Applicability; use rustc_session::Session; use rustc_span::edit_distance::edit_distance; @@ -17,8 +21,9 @@ use std::{cmp, env, fmt, fs, io}; #[rustfmt::skip] const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "MHz", "GHz", "THz", "AccessKit", - "CoreFoundation", "CoreGraphics", "CoreText", + "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", @@ -46,6 +51,29 @@ const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"]; const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] = &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]; +const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + &[ + ("modules", &[ExternCrate, Mod, ForeignMod]), + ("use", &[Use]), + ("macros", &[Macro]), + ("global_asm", &[GlobalAsm]), + ("UPPER_SNAKE_CASE", &[Static, Const]), + ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]), + ("lower_snake_case", &[Fn]), + ] +}; +const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + &[Const, Type, Fn] +}; +const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingCategory::*; + &[Enum, Impl, Module, Struct, Trait] +}; /// Conf with parse errors #[derive(Default)] @@ -102,7 +130,9 @@ pub fn sanitize_explanation(raw_docs: &str) -> String { // Remove tags and hidden code: let mut explanation = String::with_capacity(128); let mut in_code = false; - for line in raw_docs.lines().map(str::trim) { + for line in raw_docs.lines() { + let line = line.strip_prefix(' ').unwrap_or(line); + if let Some(lang) = line.strip_prefix("```") { let tag = lang.split_once(',').map_or(lang, |(left, _)| left); if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { @@ -530,6 +560,9 @@ define_Conf! { /// crate. For example, `pub(crate)` items. #[lints(missing_docs_in_private_items)] missing_docs_in_crate_items: bool = false, + /// The named groupings of different source item kinds within modules. + #[lints(arbitrary_source_item_ordering)] + module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(), /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` #[default_text = "current version"] #[lints( @@ -570,6 +603,7 @@ define_Conf! { manual_try_fold, map_clone, map_unwrap_or, + map_with_unused_argument_over_ranges, match_like_matches_macro, mem_replace_with_default, missing_const_for_fn, @@ -608,6 +642,9 @@ define_Conf! { /// The maximum number of single char bindings a scope may have #[lints(many_single_char_names)] single_char_binding_names_threshold: u64 = 4, + /// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`. + #[lints(arbitrary_source_item_ordering)] + source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(), /// The maximum allowed stack size for functions in bytes #[lints(large_stack_frames)] stack_size_threshold: u64 = 512_000, @@ -637,6 +674,9 @@ define_Conf! { /// The maximum number of lines a function or method can have #[lints(too_many_lines)] too_many_lines_threshold: u64 = 100, + /// The order of associated items in traits. + #[lints(arbitrary_source_item_ordering)] + trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(), /// 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"] diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index 42651521f8d7..1c3f32c2514c 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -20,6 +20,7 @@ extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_session; extern crate rustc_span; +extern crate smallvec; mod conf; mod metadata; diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 2f4da4cba3df..764ca8fb50ab 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -3,6 +3,7 @@ use rustc_attr::parse_version; use rustc_session::{RustcVersion, Session}; use rustc_span::{Symbol, sym}; use serde::Deserialize; +use smallvec::{SmallVec, smallvec}; use std::fmt; macro_rules! msrv_aliases { @@ -18,7 +19,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY } - 1,82,0 { IS_NONE_OR } + 1,82,0 { IS_NONE_OR, REPEAT_N } 1,81,0 { LINT_REASONS_STABILIZATION } 1,80,0 { BOX_INTO_ITER} 1,77,0 { C_STR_LITERALS } @@ -54,7 +55,7 @@ msrv_aliases! { 1,33,0 { UNDERSCORE_IMPORTS } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,29,0 { ITER_FLATTEN } - 1,28,0 { FROM_BOOL } + 1,28,0 { FROM_BOOL, REPEAT_WITH } 1,27,0 { ITERATOR_TRY_FOLD } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } 1,24,0 { IS_ASCII_DIGIT } @@ -67,7 +68,7 @@ msrv_aliases! { /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` #[derive(Debug, Clone)] pub struct Msrv { - stack: Vec, + stack: SmallVec<[RustcVersion; 2]>, } impl fmt::Display for Msrv { @@ -87,14 +88,14 @@ impl<'de> Deserialize<'de> for Msrv { { let v = String::deserialize(deserializer)?; parse_version(Symbol::intern(&v)) - .map(|v| Msrv { stack: vec![v] }) + .map(|v| Msrv { stack: smallvec![v] }) .ok_or_else(|| serde::de::Error::custom("not a valid Rust version")) } } impl Msrv { pub fn empty() -> Msrv { - Msrv { stack: Vec::new() } + Msrv { stack: SmallVec::new() } } pub fn read_cargo(&mut self, sess: &Session) { @@ -103,7 +104,7 @@ impl Msrv { .and_then(|v| parse_version(Symbol::intern(&v))); match (self.current(), cargo_msrv) { - (None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv], + (None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv], (Some(clippy_msrv), Some(cargo_msrv)) => { if clippy_msrv != cargo_msrv { sess.dcx().warn(format!( diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index bab63911182d..fe5764241487 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -1,5 +1,6 @@ use serde::de::{self, Deserializer, Visitor}; use serde::{Deserialize, Serialize, ser}; +use std::collections::HashMap; use std::fmt; #[derive(Debug, Deserialize)] @@ -102,6 +103,306 @@ impl<'de> Deserialize<'de> for MacroMatcher { } } +/// Represents the item categories that can be ordered by the source ordering lint. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingCategory { + Enum, + Impl, + Module, + Struct, + Trait, +} + +/// Represents which item categories are enabled for ordering. +/// +/// The [`Deserialize`] implementation checks that there are no duplicates in +/// the user configuration. +pub struct SourceItemOrdering(Vec); + +impl SourceItemOrdering { + pub fn contains(&self, category: &SourceItemOrderingCategory) -> bool { + self.0.contains(category) + } +} + +impl From for SourceItemOrdering +where + T: Into>, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl core::fmt::Debug for SourceItemOrdering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrdering { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let items = Vec::::deserialize(deserializer)?; + let mut items_set = std::collections::HashSet::new(); + + for item in &items { + if items_set.contains(item) { + return Err(de::Error::custom(format!( + "The category \"{item:?}\" was enabled more than once in the source ordering configuration." + ))); + } + items_set.insert(item); + } + + Ok(Self(items)) + } +} + +impl Serialize for SourceItemOrdering { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + +/// Represents the items that can occur within a module. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingModuleItemKind { + ExternCrate, + Mod, + ForeignMod, + Use, + Macro, + GlobalAsm, + Static, + Const, + TyAlias, + Enum, + Struct, + Union, + Trait, + TraitAlias, + Impl, + Fn, +} + +impl SourceItemOrderingModuleItemKind { + pub fn all_variants() -> Vec { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + vec![ + ExternCrate, + Mod, + ForeignMod, + Use, + Macro, + GlobalAsm, + Static, + Const, + TyAlias, + Enum, + Struct, + Union, + Trait, + TraitAlias, + Impl, + Fn, + ] + } +} + +/// Represents the configured ordering of items within a module. +/// +/// The [`Deserialize`] implementation checks that no item kinds have been +/// omitted and that there are no duplicates in the user configuration. +#[derive(Clone)] +pub struct SourceItemOrderingModuleItemGroupings { + groups: Vec<(String, Vec)>, + lut: HashMap, +} + +impl SourceItemOrderingModuleItemGroupings { + fn build_lut( + groups: &[(String, Vec)], + ) -> HashMap { + let mut lut = HashMap::new(); + for (group_index, (_, items)) in groups.iter().enumerate() { + for item in items { + lut.insert(item.clone(), group_index); + } + } + lut + } + + pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option { + self.lut.get(item).copied() + } +} + +impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrderingModuleItemGroupings { + fn from(value: &[(&str, &[SourceItemOrderingModuleItemKind])]) -> Self { + let groups: Vec<(String, Vec)> = + value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect(); + let lut = Self::build_lut(&groups); + Self { groups, lut } + } +} + +impl core::fmt::Debug for SourceItemOrderingModuleItemGroupings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.groups.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let groups = Vec::<(String, Vec)>::deserialize(deserializer)?; + let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum(); + let lut = Self::build_lut(&groups); + + let mut expected_items = SourceItemOrderingModuleItemKind::all_variants(); + for item in lut.keys() { + expected_items.retain(|i| i != item); + } + + let all_items = SourceItemOrderingModuleItemKind::all_variants(); + if expected_items.is_empty() && items_total == all_items.len() { + let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else { + return Err(de::Error::custom("Error in internal LUT.")); + }; + let Some((_, use_group_items)) = groups.get(*use_group_index) else { + return Err(de::Error::custom("Error in internal LUT.")); + }; + if use_group_items.len() > 1 { + return Err(de::Error::custom( + "The group containing the \"use\" item kind may not contain any other item kinds. \ + The \"use\" items will (generally) be sorted by rustfmt already. \ + Therefore it makes no sense to implement linting rules that may conflict with rustfmt.", + )); + } + + Ok(Self { groups, lut }) + } else if items_total != all_items.len() { + Err(de::Error::custom(format!( + "Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \ + The module item kinds are: {all_items:?}" + ))) + } else { + Err(de::Error::custom(format!( + "Not all module item kinds were part of the configured source ordering rule. \ + All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \ + The module item kinds are: {all_items:?}" + ))) + } + } +} + +impl Serialize for SourceItemOrderingModuleItemGroupings { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.groups.serialize(serializer) + } +} + +/// Represents all kinds of trait associated items. +#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingTraitAssocItemKind { + Const, + Fn, + Type, +} + +impl SourceItemOrderingTraitAssocItemKind { + pub fn all_variants() -> Vec { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + vec![Const, Fn, Type] + } +} + +/// Represents the order in which associated trait items should be ordered. +/// +/// The reason to wrap a `Vec` in a newtype is to be able to implement +/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks +/// on configuration completeness at the time of loading the clippy config, +/// letting the user know if there's any issues with the config (e.g. not +/// listing all item kinds that should be sorted). +#[derive(Clone)] +pub struct SourceItemOrderingTraitAssocItemKinds(Vec); + +impl SourceItemOrderingTraitAssocItemKinds { + pub fn index_of(&self, item: &SourceItemOrderingTraitAssocItemKind) -> Option { + self.0.iter().position(|i| i == item) + } +} + +impl From for SourceItemOrderingTraitAssocItemKinds +where + T: Into>, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl core::fmt::Debug for SourceItemOrderingTraitAssocItemKinds { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrderingTraitAssocItemKinds { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let items = Vec::::deserialize(deserializer)?; + + let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants(); + for item in &items { + expected_items.retain(|i| i != item); + } + + let all_items = SourceItemOrderingTraitAssocItemKind::all_variants(); + if expected_items.is_empty() && items.len() == all_items.len() { + Ok(Self(items)) + } else if items.len() != all_items.len() { + Err(de::Error::custom(format!( + "Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \ + The trait associated item kinds are: {all_items:?}", + ))) + } else { + Err(de::Error::custom(format!( + "Not all trait associated item kinds were part of the configured source ordering rule. \ + All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \ + The trait associated item kinds are: {all_items:?}" + ))) + } + } +} + +impl Serialize for SourceItemOrderingTraitAssocItemKinds { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + // these impls are never actually called but are used by the various config options that default to // empty lists macro_rules! unimplemented_serialize { diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index d367fefec619..a2d1236629fd 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -20,8 +20,14 @@ pub fn run(port: u16, lint: Option) -> ! { loop { let index_time = mtime("util/gh-pages/index.html"); + let times = [ + "clippy_lints/src", + "util/gh-pages/index_template.html", + "tests/compile-test.rs", + ] + .map(mtime); - if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") { + if times.iter().any(|&time| index_time < time) { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index d6ed36d52f4e..795456ad3c56 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -762,13 +762,19 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { Literal{..}(desc) ); - if let Some(LintDeclSearchResult { - token_kind: TokenKind::CloseBrace, - range, - .. - }) = iter.next() - { - lints.push(Lint::new(name, group, desc, module, start..range.end)); + if let Some(end) = iter.find_map(|t| { + if let LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + } = t + { + Some(range.end) + } else { + None + } + }) { + lints.push(Lint::new(name, group, desc, module, start..end)); } } } diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs new file mode 100644 index 000000000000..8719f61a8903 --- /dev/null +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -0,0 +1,531 @@ +use clippy_config::Conf; +use clippy_config::types::{ + SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, + SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, +}; +use clippy_utils::diagnostics::span_lint_and_note; +use rustc_hir::{ + AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, UseKind, + Variant, VariantData, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Confirms that items are sorted in source files as per configuration. + /// + /// ### Why restrict this? + /// + /// Keeping a consistent ordering throughout the codebase helps with working + /// as a team, and possibly improves maintainability of the codebase. The + /// idea is that by defining a consistent and enforceable rule for how + /// source files are structured, less time will be wasted during reviews on + /// a topic that is (under most circumstances) not relevant to the logic + /// implemented in the code. Sometimes this will be referred to as + /// "bikeshedding". + /// + /// ### Default Ordering and Configuration + /// + /// As there is no generally applicable rule, and each project may have + /// different requirements, the lint can be configured with high + /// granularity. The configuration is split into two stages: + /// + /// 1. Which item kinds that should have an internal order enforced. + /// 2. Individual ordering rules per item kind. + /// + /// The item kinds that can be linted are: + /// - Module (with customized groupings, alphabetical within) + /// - Trait (with customized order of associated items, alphabetical within) + /// - Enum, Impl, Struct (purely alphabetical) + /// + /// #### Module Item Order + /// + /// Due to the large variation of items within modules, the ordering can be + /// configured on a very granular level. Item kinds can be grouped together + /// arbitrarily, items within groups will be ordered alphabetically. The + /// following table shows the default groupings: + /// + /// | Group | Item Kinds | + /// |--------------------|----------------------| + /// | `modules` | "mod", "foreign_mod" | + /// | `use` | "use" | + /// | `macros` | "macro" | + /// | `global_asm` | "global_asm" | + /// | `UPPER_SNAKE_CASE` | "static", "const" | + /// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" | + /// | `lower_snake_case` | "fn" | + /// + /// All item kinds must be accounted for to create an enforceable linting + /// rule set. + /// + /// ### Known Problems + /// + /// #### Performance Impact + /// + /// Keep in mind, that ordering source code alphabetically can lead to + /// reduced performance in cases where the most commonly used enum variant + /// isn't the first entry anymore, and similar optimizations that can reduce + /// branch misses, cache locality and such. Either don't use this lint if + /// that's relevant, or disable the lint in modules or items specifically + /// where it matters. Other solutions can be to use profile guided + /// optimization (PGO), post-link optimization (e.g. using BOLT for LLVM), + /// or other advanced optimization methods. A good starting point to dig + /// into optimization is [cargo-pgo][cargo-pgo]. + /// + /// #### Lints on a Contains basis + /// + /// The lint can be disabled only on a "contains" basis, but not per element + /// within a "container", e.g. the lint works per-module, per-struct, + /// per-enum, etc. but not for "don't order this particular enum variant". + /// + /// #### Module documentation + /// + /// Module level rustdoc comments are not part of the resulting syntax tree + /// and as such cannot be linted from within `check_mod`. Instead, the + /// `rustdoc::missing_documentation` lint may be used. + /// + /// #### Module Tests + /// + /// This lint does not implement detection of module tests (or other feature + /// dependent elements for that matter). To lint the location of mod tests, + /// the lint `items_after_test_module` can be used instead. + /// + /// ### Example + /// + /// ```no_run + /// trait TraitUnordered { + /// const A: bool; + /// const C: bool; + /// const B: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn c(); + /// fn b(); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// trait TraitOrdered { + /// const A: bool; + /// const B: bool; + /// const C: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn b(); + /// fn c(); + /// } + /// ``` + /// + /// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md + /// + #[clippy::version = "1.82.0"] + pub ARBITRARY_SOURCE_ITEM_ORDERING, + restriction, + "arbitrary source item ordering" +} + +impl_lint_pass!(ArbitrarySourceItemOrdering => [ARBITRARY_SOURCE_ITEM_ORDERING]); + +#[derive(Debug)] +#[allow(clippy::struct_excessive_bools)] // Bools are cached feature flags. +pub struct ArbitrarySourceItemOrdering { + assoc_types_order: SourceItemOrderingTraitAssocItemKinds, + enable_ordering_for_enum: bool, + enable_ordering_for_impl: bool, + enable_ordering_for_module: bool, + enable_ordering_for_struct: bool, + enable_ordering_for_trait: bool, + module_item_order_groupings: SourceItemOrderingModuleItemGroupings, +} + +impl ArbitrarySourceItemOrdering { + pub fn new(conf: &'static Conf) -> Self { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingCategory::*; + Self { + assoc_types_order: conf.trait_assoc_item_kinds_order.clone(), + enable_ordering_for_enum: conf.source_item_ordering.contains(&Enum), + enable_ordering_for_impl: conf.source_item_ordering.contains(&Impl), + enable_ordering_for_module: conf.source_item_ordering.contains(&Module), + enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct), + enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait), + module_item_order_groupings: conf.module_item_order_groupings.clone(), + } + } + + /// Produces a linting warning for incorrectly ordered impl items. + fn lint_impl_item(&self, cx: &T, item: &ImplItemRef, before_item: &ImplItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of impl items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } + + /// Produces a linting warning for incorrectly ordered item members. + fn lint_member_name( + cx: &T, + ident: &rustc_span::symbol::Ident, + before_ident: &rustc_span::symbol::Ident, + ) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + ident.span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(before_ident.span), + format!("should be placed before `{}`", before_ident.as_str(),), + ); + } + + fn lint_member_item(cx: &T, item: &Item<'_>, before_item: &Item<'_>) { + let span = if item.ident.as_str().is_empty() { + &item.span + } else { + &item.ident.span + }; + + let (before_span, note) = if before_item.ident.as_str().is_empty() { + ( + &before_item.span, + "should be placed before the following item".to_owned(), + ) + } else { + ( + &before_item.ident.span, + format!("should be placed before `{}`", before_item.ident.as_str(),), + ) + }; + + // This catches false positives where generated code gets linted. + if span == before_span { + return; + } + + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + *span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(*before_span), + note, + ); + } + + /// Produces a linting warning for incorrectly ordered trait items. + fn lint_trait_item(&self, cx: &T, item: &TraitItemRef, before_item: &TraitItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of trait items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + match &item.kind { + ItemKind::Enum(enum_def, _generics) if self.enable_ordering_for_enum => { + let mut cur_v: Option<&Variant<'_>> = None; + for variant in enum_def.variants { + if in_external_macro(cx.sess(), variant.span) { + continue; + } + + if let Some(cur_v) = cur_v { + if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span { + Self::lint_member_name(cx, &variant.ident, &cur_v.ident); + } + } + cur_v = Some(variant); + } + }, + ItemKind::Struct(VariantData::Struct { fields, .. }, _generics) if self.enable_ordering_for_struct => { + let mut cur_f: Option<&FieldDef<'_>> = None; + for field in *fields { + if in_external_macro(cx.sess(), field.span) { + continue; + } + + if let Some(cur_f) = cur_f { + if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span { + Self::lint_member_name(cx, &field.ident, &cur_f.ident); + } + } + cur_f = Some(field); + } + }, + ItemKind::Trait(is_auto, _safety, _generics, _generic_bounds, item_ref) + if self.enable_ordering_for_trait && *is_auto == IsAuto::No => + { + let mut cur_t: Option<&TraitItemRef> = None; + + for item in *item_ref { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_trait_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + ItemKind::Impl(trait_impl) if self.enable_ordering_for_impl => { + let mut cur_t: Option<&ImplItemRef> = None; + + for item in trait_impl.items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_impl_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + _ => {}, // Catch-all for `ItemKinds` that don't have fields. + } + } + + fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) { + struct CurItem<'a> { + item: &'a Item<'a>, + order: usize, + name: String, + } + let mut cur_t: Option> = None; + + if !self.enable_ordering_for_module { + return; + } + + let items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id)); + + // Iterates over the items within a module. + // + // As of 2023-05-09, the Rust compiler will hold the entries in the same + // order as they appear in the source code, which is convenient for us, + // as no sorting by source map/line of code has to be applied. + // + for item in items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + // The following exceptions (skipping with `continue;`) may not be + // complete, edge cases have not been explored further than what + // appears in the existing code base. + if item.ident.name == rustc_span::symbol::kw::Empty { + if let ItemKind::Impl(_) = item.kind { + // Sorting trait impls for unnamed types makes no sense. + if get_item_name(item).is_empty() { + continue; + } + } else if let ItemKind::ForeignMod { .. } = item.kind { + continue; + } else if let ItemKind::GlobalAsm(_) = item.kind { + continue; + } else if let ItemKind::Use(path, use_kind) = item.kind { + if path.segments.is_empty() { + // Use statements that contain braces get caught here. + // They will still be linted internally. + continue; + } else if path.segments.len() >= 2 + && (path.segments[0].ident.name == rustc_span::sym::std + || path.segments[0].ident.name == rustc_span::sym::core) + && path.segments[1].ident.name == rustc_span::sym::prelude + { + // Filters the autogenerated prelude use statement. + // e.g. `use std::prelude::rustc_2021` + } else if use_kind == UseKind::Glob { + // Filters glob kinds of uses. + // e.g. `use std::sync::*` + } else { + // This can be used for debugging. + // println!("Unknown autogenerated use statement: {:?}", item); + } + continue; + } + } + + if item.ident.name.as_str().starts_with('_') { + // Filters out unnamed macro-like impls for various derives, + // e.g. serde::Serialize or num_derive::FromPrimitive. + continue; + } + + if item.ident.name == rustc_span::sym::std && item.span.is_dummy() { + if let ItemKind::ExternCrate(None) = item.kind { + // Filters the auto-included Rust standard library. + continue; + } + println!("Unknown item: {item:?}"); + } + + let item_kind = convert_module_item_kind(&item.kind); + let module_level_order = self + .module_item_order_groupings + .module_level_order_of(&item_kind) + .unwrap_or_default(); + + if let Some(cur_t) = cur_t.as_ref() { + use std::cmp::Ordering; // Better legibility. + match module_level_order.cmp(&cur_t.order) { + Ordering::Less => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => { + // Skip ordering use statements, as these should be ordered by rustfmt. + }, + Ordering::Equal if cur_t.name > get_item_name(item) => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal | Ordering::Greater => { + // Nothing to do in this case, they're already in the right order. + }, + } + } + + // Makes a note of the current item for comparison with the next. + cur_t = Some(CurItem { + order: module_level_order, + item, + name: get_item_name(item), + }); + } + } +} + +/// Converts a [`rustc_hir::AssocItemKind`] to a +/// [`SourceItemOrderingTraitAssocItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_assoc_item_kind(value: AssocItemKind) -> SourceItemOrderingTraitAssocItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + match value { + AssocItemKind::Const { .. } => Const, + AssocItemKind::Type { .. } => Type, + AssocItemKind::Fn { .. } => Fn, + } +} + +/// Converts a [`rustc_hir::ItemKind`] to a +/// [`SourceItemOrderingModuleItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + match value { + ItemKind::ExternCrate(..) => ExternCrate, + ItemKind::Use(..) => Use, + ItemKind::Static(..) => Static, + ItemKind::Const(..) => Const, + ItemKind::Fn(..) => Fn, + ItemKind::Macro(..) => Macro, + ItemKind::Mod(..) => Mod, + ItemKind::ForeignMod { .. } => ForeignMod, + ItemKind::GlobalAsm(..) => GlobalAsm, + ItemKind::TyAlias(..) => TyAlias, + ItemKind::Enum(..) => Enum, + ItemKind::Struct(..) => Struct, + ItemKind::Union(..) => Union, + ItemKind::Trait(..) => Trait, + ItemKind::TraitAlias(..) => TraitAlias, + ItemKind::Impl(..) => Impl, + } +} + +/// Gets the item name for sorting purposes, which in the general case is +/// `item.ident.name`. +/// +/// For trait impls, the name used for sorting will be the written path of +/// `item.self_ty` plus the written path of `item.of_trait`, joined with +/// exclamation marks. Exclamation marks are used because they are the first +/// printable ASCII character. +/// +/// Trait impls generated using a derive-macro will have their path rewritten, +/// such that for example `Default` is `$crate::default::Default`, and +/// `std::clone::Clone` is `$crate::clone::Clone`. This behaviour is described +/// further in the [Rust Reference, Paths Chapter][rust_ref]. +/// +/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1 +fn get_item_name(item: &Item<'_>) -> String { + match item.kind { + ItemKind::Impl(im) => { + if let TyKind::Path(path) = im.self_ty.kind { + match path { + QPath::Resolved(_, path) => { + let segs = path.segments.iter(); + let mut segs: Vec = segs.map(|s| s.ident.name.as_str().to_owned()).collect(); + + if let Some(of_trait) = im.of_trait { + let mut trait_segs: Vec = of_trait + .path + .segments + .iter() + .map(|s| s.ident.name.as_str().to_owned()) + .collect(); + segs.append(&mut trait_segs); + } + + segs.push(String::new()); + segs.join("!!") + }, + QPath::TypeRelative(_, _path_seg) => { + // This case doesn't exist in the clippy tests codebase. + String::new() + }, + QPath::LangItem(_, _) => String::new(), + } + } else { + // Impls for anything that isn't a named type can be skipped. + String::new() + } + }, + _ => item.ident.name.as_str().to_owned(), + } +} diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 684756ce87f1..c29c9f08cf70 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -14,7 +14,7 @@ mod utils; use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; -use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast}; +use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 92b9f9cba525..dc5a68570896 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -1,10 +1,9 @@ -use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use super::USELESS_ATTRIBUTE; +use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, first_line_of_span}; -use rustc_ast::MetaItemInner; +use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner}; use rustc_errors::Applicability; -use rustc_ast::{Item, ItemKind, Attribute}; use rustc_lint::{EarlyContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_span::sym; diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 9952d0af99e5..6948cf560a53 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// `MutexGuard`. /// /// ### Why is this bad? - /// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and + /// The Mutex types found in [`std::sync`](https://doc.rust-lang.org/stable/std/sync/) and /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are /// not designed to operate in an async context across await points. /// diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 26888f7e3a0e..26a20bc99a05 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) { && let Some(suggestion) = simplify_not(cx, msrv, inner) && cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow { + use clippy_utils::sugg::{Sugg, has_enclosing_paren}; + let maybe_par = if let Some(sug) = Sugg::hir_opt(cx, inner) { + match sug { + Sugg::BinOp(..) => true, + Sugg::MaybeParen(sug) if !has_enclosing_paren(&sug) => true, + _ => false, + } + } else { + false + }; + let suggestion = if maybe_par { + format!("({suggestion})") + } else { + suggestion + }; span_lint_and_sugg( cx, NONMINIMAL_BOOL, diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index cba8224b84c9..f2551a05b1a2 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -4,7 +4,7 @@ use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed}; use rustc_errors::Applicability; -use rustc_hir::{ExprKind, UnOp}; +use rustc_hir::{BorrowKind, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::Mutability; use rustc_middle::ty; @@ -49,7 +49,7 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]); impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { - if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..)) && !e.span.from_expansion() diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index b11b967f8bc7..205357afd840 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { ); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind { - if method_path.ident.name == sym!(cast) + if method_path.ident.name.as_str() == "cast" && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args // There probably is no obvious reason to do this, just to be consistent with `as` cases. diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 811d33c8bde2..abd80abffe69 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -268,8 +268,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx if !snippet .split("->") .skip(1) - .map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) - .any(|a| a) + .any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) { return ControlFlow::Break(()); } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 2e7f91a842e1..f76e399517cf 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -232,7 +232,7 @@ fn get_types_from_cast<'a>( // or `to_type::MAX as from_type` let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind // to_type::max_value(), from_type - && let TyKind::Path(ref from_type_path) = &from_type.kind + && let TyKind::Path(from_type_path) = &from_type.kind && let Some(from_sym) = int_ty_to_sym(from_type_path) { Some((limit, from_sym)) @@ -245,7 +245,7 @@ fn get_types_from_cast<'a>( if let ExprKind::Call(from_func, [limit]) = &expr.kind // `from_type::from, to_type::max_value()` // `from_type::from` - && let ExprKind::Path(ref path) = &from_func.kind + && let ExprKind::Path(path) = &from_func.kind && let Some(from_sym) = get_implementing_type(path, INTS, "from") { Some((limit, from_sym)) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 477435236a52..383fae7992b6 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { #[clippy::version = "1.35.0"] pub COGNITIVE_COMPLEXITY, nursery, - "functions that should be split up into multiple functions" + "functions that should be split up into multiple functions", @eval_always = true } diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index d90a22bb62a6..c4afdc757d81 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo .iter() .filter(|&&(_, name)| !name.as_str().starts_with('_')) .any(|&(_, name)| { - let mut walker = ContainsName { - name, - result: false, - cx, - }; + let mut walker = ContainsName { name, cx }; // Scan block - block + let mut res = block .stmts .iter() .filter(|stmt| !ignore_span.overlaps(stmt.span)) - .for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); + .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); if let Some(expr) = block.expr { - intravisit::walk_expr(&mut walker, expr); + if res.is_continue() { + res = intravisit::walk_expr(&mut walker, expr); + } } - walker.result + res.is_break() }) }) } diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs index 50fd76a3a477..4ecf3e41611b 100644 --- a/clippy_lints/src/copy_iterator.rs +++ b/clippy_lints/src/copy_iterator.rs @@ -37,7 +37,7 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]); impl<'tcx> LateLintPass<'tcx> for CopyIterator { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() diff --git a/clippy_lints/src/ctfe.rs b/clippy_lints/src/ctfe.rs index 181514e8372b..589b99518a09 100644 --- a/clippy_lints/src/ctfe.rs +++ b/clippy_lints/src/ctfe.rs @@ -5,7 +5,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; - declare_lint_pass! { /// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran. diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs index a785a9d377c5..4d908af4084f 100644 --- a/clippy_lints/src/declare_clippy_lint.rs +++ b/clippy_lints/src/declare_clippy_lint.rs @@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint { (@ $(#[doc = $lit:literal])* pub $lint_name:ident, - $category:ident, + $level:ident, $lintcategory:expr, $desc:literal, $version_expr:expr, @@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint { $(#[doc = $lit])* #[clippy::version = $version_lit] pub clippy::$lint_name, - $category, + $level, $desc, report_in_external_macro:true $(, @eval_always = $eval_always)? @@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, restriction, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -49,12 +50,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, style, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Style, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -63,12 +65,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, correctness, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; @@ -78,12 +81,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, perf, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Perf, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -92,12 +96,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, complexity, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -106,12 +111,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, suspicious, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -120,12 +126,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, nursery, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -134,12 +141,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, pedantic, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -148,12 +156,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, cargo, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3c4e75df8abe..edb52851e0cb 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -28,12 +28,15 @@ pub static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, #[cfg(feature = "internal")] + crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO, + #[cfg(feature = "internal")] crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO, crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, + crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO, crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO, crate::as_conversions::AS_CONVERSIONS_INFO, crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, @@ -414,14 +417,17 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_SPLIT_ONCE_INFO, crate::methods::MANUAL_STR_REPEAT_INFO, crate::methods::MANUAL_TRY_FOLD_INFO, + crate::methods::MAP_ALL_ANY_IDENTITY_INFO, crate::methods::MAP_CLONE_INFO, crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, crate::methods::MAP_ERR_IGNORE_INFO, crate::methods::MAP_FLATTEN_INFO, crate::methods::MAP_IDENTITY_INFO, crate::methods::MAP_UNWRAP_OR_INFO, + crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, crate::methods::MUT_MUTEX_LOCK_INFO, crate::methods::NAIVE_BYTECOUNT_INFO, + crate::methods::NEEDLESS_AS_BYTES_INFO, crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, crate::methods::NEEDLESS_COLLECT_INFO, crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 2920bbb4c81e..2b6bfafe695b 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -187,7 +187,7 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex impl<'tcx> LateLintPass<'tcx> for DerivableImpls { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], self_ty, .. diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index e8e21edd4943..e569c4dc7863 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; @@ -202,7 +204,7 @@ declare_lint_pass!(Derive => [ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind { @@ -371,9 +373,8 @@ fn check_unsafe_derive_deserialize<'tcx>( ty: Ty<'tcx>, ) { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { - let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; - walk_item(&mut visitor, item); - visitor.has_unsafe + let mut visitor = UnsafeVisitor { cx }; + walk_item(&mut visitor, item).is_break() } if let Some(trait_def_id) = trait_ref.trait_def_id() @@ -406,38 +407,37 @@ fn check_unsafe_derive_deserialize<'tcx>( struct UnsafeVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - has_unsafe: bool, } impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { - if self.has_unsafe { - return; - } - + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) -> Self::Result { if let Some(header) = kind.header() && header.safety == Safety::Unsafe { - self.has_unsafe = true; + ControlFlow::Break(()) + } else { + walk_fn(self, kind, decl, body_id, id) } - - walk_fn(self, kind, decl, body_id, id); } - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.has_unsafe { - return; - } - + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::Block(block, _) = expr.kind { if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - self.has_unsafe = true; + return ControlFlow::Break(()); } } - walk_expr(self, expr); + walk_expr(self, expr) } fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index b51d343132b2..bdd49bf8aa7c 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -2,7 +2,6 @@ use clippy_config::Conf; use clippy_utils::create_disallowed_map; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::macros::macro_backtrace; -use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Diag; use rustc_hir::def_id::DefIdMap; @@ -14,6 +13,8 @@ use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{ExpnId, MacroKind, Span}; +use crate::utils::attr_collector::AttrStorage; + declare_clippy_lint! { /// ### What it does /// Denies the configured macros in clippy.toml @@ -64,14 +65,19 @@ pub struct DisallowedMacros { // Track the most recently seen node that can have a `derive` attribute. // Needed to use the correct lint level. derive_src: Option, + + // When a macro is disallowed in an early pass, it's stored + // and emitted during the late pass. This happens for attributes. + earlies: AttrStorage, } impl DisallowedMacros { - pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { Self { disallowed: create_disallowed_map(tcx, &conf.disallowed_macros), seen: FxHashSet::default(), derive_src: None, + earlies, } } @@ -114,6 +120,15 @@ impl DisallowedMacros { impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl LateLintPass<'_> for DisallowedMacros { + fn check_crate(&mut self, cx: &LateContext<'_>) { + // once we check a crate in the late pass we can emit the early pass lints + if let Some(attr_spans) = self.earlies.clone().0.get() { + for span in attr_spans { + self.check(cx, *span, None); + } + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { self.check(cx, expr.span, None); // `$t + $t` can have the context of $t, check also the span of the binary operator @@ -164,8 +179,4 @@ impl LateLintPass<'_> for DisallowedMacros { fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { self.check(cx, path.span, None); } - - fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { - self.check(cx, attr.span, self.derive_src); - } } diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index f79264e6b04a..53c24a3faf1d 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -80,7 +80,7 @@ impl EarlyLintPass for DisallowedScriptIdents { let mut symbols: Vec<_> = symbols.iter().collect(); symbols.sort_unstable_by_key(|k| k.1); - for (symbol, &span) in &symbols { + for &(symbol, &span) in &symbols { // Note: `symbol.as_str()` is an expensive operation, thus should not be called // more than once for a single symbol. let symbol_str = symbol.as_str(); diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 89c6a4e08dc4..df7c37a192ad 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -427,11 +427,11 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks if the first line in the documentation of items listed in module page is too long. + /// Checks if the first paragraph in the documentation of items listed in the 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. + /// Documentation will show the first paragraph of the docstring in the summary page of a + /// module. Having a nice, short summary in the first paragraph is part of writing good docs. /// /// ### Example /// ```no_run @@ -453,7 +453,7 @@ declare_clippy_lint! { #[clippy::version = "1.82.0"] pub TOO_LONG_FIRST_DOC_PARAGRAPH, nursery, - "ensure that the first line of a documentation paragraph isn't too long" + "ensure the first documentation paragraph is short" } declare_clippy_lint! { diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index b66dd2108fc9..10a84b1b2ff2 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -36,7 +36,7 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]); impl LateLintPass<'_> for EmptyDrop { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], .. }) = item.kind diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 6ca599ed3611..de10b7bf5339 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, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, - TypeckResults, + self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -204,11 +203,16 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc // '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(cx.typing_mode()).err_ctxt().type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { + match cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .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() => { diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 4e4434ec7d19..0550c22761a5 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { // match call to write_fmt && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind) && let ExprKind::Call(write_recv_path, []) = write_recv.kind - && write_fun.ident.name == sym!(write_fmt) + && write_fun.ident.name.as_str() == "write_fmt" && let Some(def_id) = path_def_id(cx, write_recv_path) { // match calls to std::io::stdout() / std::io::stderr () diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index d5a2e06863dc..14da0b515a51 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; @@ -115,25 +117,26 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { } /// Finds the occurrences of `Self` and `self` +/// +/// Returns `ControlFlow::break` if any of the `self`/`Self` usages were from an expansion, or the +/// body contained a binding already named `val`. struct SelfFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// Occurrences of `Self` upper: Vec, /// Occurrences of `self` lower: Vec, - /// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding - /// already named `val` - invalid: bool, } impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { + fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result { for segment in path.segments { match segment.ident.name { kw::SelfLower => self.lower.push(segment.ident.span), @@ -141,17 +144,19 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { _ => continue, } - self.invalid |= segment.ident.span.from_expansion(); + if segment.ident.span.from_expansion() { + return ControlFlow::Break(()); + } } - if !self.invalid { - walk_path(self, path); - } + walk_path(self, path) } - fn visit_name(&mut self, name: Symbol) { + fn visit_name(&mut self, name: Symbol) -> Self::Result { if name == sym::val { - self.invalid = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } } @@ -209,11 +214,9 @@ fn convert_to_from( cx, upper: Vec::new(), lower: Vec::new(), - invalid: false, }; - finder.visit_expr(body.value); - if finder.invalid { + if finder.visit_expr(body.value).is_break() { return None; } diff --git a/clippy_lints/src/from_raw_with_void_ptr.rs b/clippy_lints/src/from_raw_with_void_ptr.rs index d62d008d480f..c8828c936157 100644 --- a/clippy_lints/src/from_raw_with_void_ptr.rs +++ b/clippy_lints/src/from_raw_with_void_ptr.rs @@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Call(box_from_raw, [arg]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind - && seg.ident.name == sym!(from_raw) + && seg.ident.name.as_str() == "from_raw" && let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id)) && let arg_kind = cx.typeck_results().expr_ty(arg).kind() && let ty::RawPtr(ty, _) = arg_kind diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 91d73c2a9c96..be3d0f7ad634 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -441,7 +441,7 @@ declare_clippy_lint! { /// fn bar(&self) -> Option<&String> { None } /// # } /// ``` - #[clippy::version = "1.82.0"] + #[clippy::version = "1.83.0"] pub REF_OPTION, pedantic, "function signature uses `&Option` instead of `Option<&T>`" diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index 373ecd74cb37..aba0fbcb9feb 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -15,9 +15,9 @@ use rustc_span::{Span, sym}; fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() && is_type_diagnostic_item(cx, *opt_ty, sym::Option) - && let ty::Adt(_, opt_gen) = opt_ty.kind() - && let [gen] = opt_gen.as_slice() - && let GenericArgKind::Type(gen_ty) = gen.unpack() + && let ty::Adt(_, opt_gen_args) = opt_ty.kind() + && let [gen_arg] = opt_gen_args.as_slice() + && let GenericArgKind::Type(gen_ty) = gen_arg.unpack() && !gen_ty.is_ref() // Need to gen the original spans, so first parsing mid, and hir parsing afterward && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 4c5375730b8e..c370f206c8f8 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -340,7 +340,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashMap::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( @@ -352,7 +352,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashSet::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 71c317552ee1..48874d6064b6 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { .and(cap); } } - if method.ident.name == sym!(flat_map) && args.len() == 1 { + if method.ident.name.as_str() == "flat_map" && args.len() == 1 { if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind { let body = cx.tcx.hir().body(body); return is_infinite(cx, body.value); @@ -224,7 +224,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { return MaybeInfinite.and(is_infinite(cx, receiver)); } } - if method.ident.name == sym!(last) && args.is_empty() { + if method.ident.name.as_str() == "last" && args.is_empty() { let not_double_ended = cx .tcx .get_diagnostic_item(sym::DoubleEndedIterator) @@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { if not_double_ended { return is_infinite(cx, receiver); } - } else if method.ident.name == sym!(collect) { + } else if method.ident.name.as_str() == "collect" { let ty = cx.typeck_results().expr_ty(expr); if matches!( get_type_diagnostic_name(cx, ty), diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 1e6404190d3c..314d0dfa26ce 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -142,7 +142,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter { ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() }) && let Some(iter_assoc_span) = imp.items.iter().find_map(|item| { - if item.ident.name == sym!(IntoIter) { + if item.ident.name.as_str() == "IntoIter" { Some(cx.tcx.hir().impl_item(item.id).expect_type().span) } else { None @@ -247,8 +247,8 @@ impl {self_ty_without_ref} {{ let sugg = format!( " impl IntoIterator for {self_ty_snippet} {{ - type IntoIter = {ret_ty}; type Item = {iter_ty}; + type IntoIter = {ret_ty}; fn into_iter(self) -> Self::IntoIter {{ self.iter() }} diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 5d2b521b2505..aa8b4d88d2f2 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, ConstKind}; +use rustc_middle::ty::{self, ParamEnv}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Pos, Span}; @@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && !item.span.from_expansion() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() - && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() + && let Ok((_, ty::ValTree::Leaf(element_count))) = cst.eval_valtree(cx.tcx, ParamEnv::empty(), item.span) && let element_count = element_count.to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index f2f841dcec33..ab3d19f89c3f 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,11 +1,12 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; -use rustc_ast::LitKind; +use clippy_utils::source::snippet_opt; +use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind, Attribute, LitKind}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::sym; +use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -51,6 +52,24 @@ impl LargeIncludeFile { impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); +impl LargeIncludeFile { + fn emit_lint(&self, cx: &LateContext<'_>, span: Span) { + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + cx, + LARGE_INCLUDE_FILE, + span, + "attempted to include a large file", + |diag| { + diag.note(format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + )); + }, + ); + } +} + impl LateLintPass<'_> for LargeIncludeFile { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Lit(lit) = &expr.kind @@ -66,19 +85,33 @@ 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)) { - #[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", - |diag| { - diag.note(format!( - "the configuration allows a maximum size of {} bytes", - self.max_file_size - )); - }, - ); + self.emit_lint(cx, expr.span.source_callsite()); + } + } + + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { + if !attr.span.from_expansion() + // Currently, rustc limits the usage of macro at the top-level of attributes, + // so we don't need to recurse into each level. + && let AttrKind::Normal(ref normal) = attr.kind + && let AttrArgs::Eq(_, AttrArgsEq::Hir(ref meta)) = normal.item.args + && !attr.span.contains(meta.span) + // Since the `include_str` is already expanded at this point, we can only take the + // whole attribute snippet and then modify for our suggestion. + && let Some(snippet) = snippet_opt(cx, attr.span) + // We cannot remove this because a `#[doc = include_str!("...")]` attribute can + // occupy several lines. + && let Some(start) = snippet.find('[') + && let Some(end) = snippet.rfind(']') + && let snippet = &snippet[start + 1..end] + // We check that the expansion actually comes from `include_str!` and not just from + // another macro. + && let Some(sub_snippet) = snippet.trim().strip_prefix("doc") + && let Some(sub_snippet) = sub_snippet.trim().strip_prefix("=") + && let sub_snippet = sub_snippet.trim() + && (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!")) + { + self.emit_lint(cx, attr.span); } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 47c65ee6d0ba..b7887ef76a53 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -629,7 +629,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { .filter_by_name_unhygienic(is_empty) .any(|item| is_is_empty(cx, item)) }), - ty::Alias(ty::Projection, ref proj) => has_is_empty_impl(cx, proj.def_id), + ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), ty::Adt(id, _) => has_is_empty_impl(cx, id.did()), ty::Array(..) | ty::Slice(..) | ty::Str => true, _ => false, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3fd07ced0e4a..c9064df25ac8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -73,6 +73,7 @@ pub mod deprecated_lints; mod absolute_paths; mod almost_complete_range; mod approx_const; +mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; mod asm_syntax; @@ -400,6 +401,7 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; +use utils::attr_collector::{AttrCollector, AttrStorage}; /// Register all pre expansion lints /// @@ -464,6 +466,7 @@ pub(crate) enum LintCategory { #[cfg(feature = "internal")] Internal, } + #[allow(clippy::enum_glob_use)] use LintCategory::*; @@ -585,6 +588,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { )) }); + let attr_storage = AttrStorage::default(); + let attrs = attr_storage.clone(); + store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); + // all the internal lints #[cfg(feature = "internal")] { @@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| { Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new()) }); + store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons)); } store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe)); @@ -795,7 +803,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); - store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf))); + let attrs = attr_storage.clone(); + store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); @@ -953,5 +962,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); + store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 119e410b91c7..ee561ea85ed1 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -29,11 +29,7 @@ pub(super) fn check( if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) { return; } - } else if count - .try_to_target_usize(cx.tcx) - .map_or(true, |x| x > 32) - && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) - { + } else if count.try_to_target_usize(cx.tcx).map_or(true, |x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) { return; } } diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index e25c03db5344..9f543b79bac4 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -1,12 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use hir::intravisit::{Visitor, walk_expr}; -use hir::{Expr, ExprKind, FnRetTy, FnSig, Node}; +use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; +use rustc_span::sym; use super::INFINITE_LOOP; @@ -25,13 +26,7 @@ pub(super) fn check<'tcx>( return; }; // Or, its parent function is already returning `Never` - if matches!( - parent_fn_ret, - FnRetTy::Return(hir::Ty { - kind: hir::TyKind::Never, - .. - }) - ) { + if is_never_return(parent_fn_ret) { return; } @@ -69,6 +64,16 @@ pub(super) fn check<'tcx>( fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) { match parent_node { + // Skip `Coroutine` closures, these are the body of `async fn`, not async closures. + // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty. + Node::Expr(Expr { + kind: + ExprKind::Closure(hir::Closure { + kind: hir::ClosureKind::Coroutine(_), + .. + }), + .. + }) => (), Node::Item(hir::Item { kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _), .. @@ -143,3 +148,41 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> { } } } + +/// Return `true` if the given [`FnRetTy`] is never (!). +/// +/// Note: This function also take care of return type of async fn, +/// as the actual type is behind an [`OpaqueDef`](TyKind::OpaqueDef). +fn is_never_return(ret_ty: FnRetTy<'_>) -> bool { + let FnRetTy::Return(hir_ty) = ret_ty else { return false }; + + match hir_ty.kind { + TyKind::Never => true, + TyKind::OpaqueDef(hir::OpaqueTy { + origin: hir::OpaqueTyOrigin::AsyncFn { .. }, + bounds, + .. + }) => { + if let Some(trait_ref) = bounds.iter().find_map(|b| b.trait_ref()) + && let Some(segment) = trait_ref + .path + .segments + .iter() + .find(|seg| seg.ident.name == sym::future_trait) + && let Some(args) = segment.args + && let Some(cst_kind) = args + .constraints + .iter() + .find_map(|cst| (cst.ident.name == sym::Output).then_some(cst.kind)) + && let hir::AssocItemConstraintKind::Equality { + term: hir::Term::Ty(ty), + } = cst_kind + { + matches!(ty.kind, TyKind::Never) + } else { + false + } + }, + _ => false, + } +} diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index 1a1cde3c5bd6..7da4fa76e2c7 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -19,10 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &' cx, ids: HirIdSet::default(), def_ids: DefIdMap::default(), - skip: false, }; - var_visitor.visit_expr(cond); - if var_visitor.skip { + if var_visitor.visit_expr(cond).is_break() { return; } let used_in_condition = &var_visitor.ids; @@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, ids: HirIdSet, def_ids: DefIdMap, - skip: bool, } impl<'tcx> VarCollectorVisitor<'_, 'tcx> { @@ -104,11 +101,15 @@ impl<'tcx> VarCollectorVisitor<'_, 'tcx> { } impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + type Result = ControlFlow<()>; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { match ex.kind { - ExprKind::Path(_) => self.insert_def_id(ex), + ExprKind::Path(_) => { + self.insert_def_id(ex); + ControlFlow::Continue(()) + }, // If there is any function/method call… we just stop analysis - ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + ExprKind::Call(..) | ExprKind::MethodCall(..) => ControlFlow::Break(()), _ => walk_expr(self, ex), } diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 74467522619e..b7e37c1a876f 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; @@ -204,35 +206,32 @@ fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tc struct V<'a, 'b, 'tcx> { cx: &'a LateContext<'tcx>, iter_expr: &'b IterExpr, - uses_iter: bool, } impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> { - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.uses_iter { - // return - } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { - self.uses_iter = true; + type Result = ControlFlow<()>; + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { + if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { if is_res_used(self.cx, self.iter_expr.path, id) { - self.uses_iter = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } else { - walk_expr(self, e); + walk_expr(self, e) } } } - let mut v = V { - cx, - iter_expr, - uses_iter: false, - }; - v.visit_expr(container); - v.uses_iter + let mut v = V { cx, iter_expr }; + v.visit_expr(container).is_break() } #[expect(clippy::too_many_lines)] @@ -242,34 +241,38 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & iter_expr: &'b IterExpr, loop_id: HirId, after_loop: bool, - used_iter: bool, } impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { type NestedFilter = OnlyBodies; + type Result = ControlFlow<()>; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.used_iter { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { if self.after_loop { if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { - self.used_iter = true; + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { - self.used_iter = is_res_used(self.cx, self.iter_expr.path, id); + if is_res_used(self.cx, self.iter_expr.path, id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } } else { - walk_expr(self, e); + walk_expr(self, e) } } else if self.loop_id == e.hir_id { self.after_loop = true; + ControlFlow::Continue(()) } else { - walk_expr(self, e); + walk_expr(self, e) } } } @@ -347,9 +350,8 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & iter_expr, loop_id: loop_expr.hir_id, after_loop: false, - used_iter: false, }; - v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value); - v.used_iter + v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value) + .is_break() } } diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index c904137da1a1..3c77db84a405 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -106,13 +106,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>) -> Option<&'tcx TraitRef<'tcx>> { if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { - if let GenericBound::Trait(poly) = bound { - Some(&poly.trait_ref) - } else { - None - } - }) - && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() + if let GenericBound::Trait(poly) = bound { + Some(&poly.trait_ref) + } else { + None + } + }) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() { return Some(trait_ref); } @@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de .tcx .opaque_captured_lifetimes(opaque_def_id) .iter() - .filter(|&(lifetime, _)| match *lifetime { - ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true, - _ => false, + .filter(|&(lifetime, _)| { + matches!( + *lifetime, + ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) + ) }) .count(); num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes } fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { - if let Some(Expr { + if let Some(&Expr { kind: ExprKind::Closure(&Closure { kind, body, .. }), .. }) = block.expr diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 1bd8813e3480..fd71167f814d 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -7,8 +7,7 @@ use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -53,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind && let BinOpKind::Mul = &bin_op.node - && !in_external_macro(cx.sess(), expr.span) + && !expr.span.from_expansion() && self.msrv.meets(msrvs::MANUAL_BITS) && let ctxt = expr.span.ctxt() && left_expr.span.ctxt() == ctxt diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs index c62bd65bea68..7a9c99637428 100644 --- a/clippy_lints/src/manual_hash_one.rs +++ b/clippy_lints/src/manual_hash_one.rs @@ -68,7 +68,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Some(init) = local.init && !init.span.from_expansion() && let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind - && seg.ident.name == sym!(build_hasher) + && seg.ident.name.as_str() == "build_hasher" && let Node::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id) && let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.hir_id) @@ -96,7 +96,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id) && !finish_expr.span.from_expansion() && let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind - && seg.ident.name == sym!(finish) + && seg.ident.name.as_str() == "finish" && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE) { diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 24207705743d..dec8c5d85dec 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { check_is_ascii(cx, macro_call.span, recv, &range, None); } } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind - && path.ident.name == sym!(contains) + && path.ident.name.as_str() == "contains" && let Some(higher::Range { start: Some(start), end: Some(end), diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 64cb7a06ce9c..6d62b530ae7e 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -148,7 +148,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option { }) => Some(*b), ExprKind::Block( rustc_hir::Block { - stmts: &[], + stmts: [], expr: Some(exp), .. }, diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 463aa602bc8b..1267fc9d0a53 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; @@ -23,11 +25,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind() && let ty::Str = ty.kind() { - let mut visitor = MatchExprVisitor { cx, case_method: None }; - - visitor.visit_expr(scrutinee); - - if let Some(case_method) = visitor.case_method { + let mut visitor = MatchExprVisitor { cx }; + if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) { if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) { lint(cx, &case_method, bad_case_span, bad_case_sym.as_str()); } @@ -37,30 +36,33 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm struct MatchExprVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - case_method: Option, } impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - match ex.kind { - ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {}, - _ => walk_expr(self, ex), + type Result = ControlFlow; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { + if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind { + let result = self.case_altered(segment.ident.as_str(), receiver); + if result.is_break() { + return result; + } } + + walk_expr(self, ex) } } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool { + fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str { - self.case_method = Some(case_method); - return true; + return ControlFlow::Break(case_method); } } - false + ControlFlow::Continue(()) } } diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 42d9efe4ff6f..9e54475033c8 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp}; use rustc_lint::LateContext; use rustc_span::symbol::Ident; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, sym}; use std::borrow::Cow; use std::ops::ControlFlow; @@ -95,7 +95,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind && let Some(binding) = get_pat_binding(cx, recv, outer_arm) { - check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding); + check_method_calls(cx, outer_arm, path.ident.name.as_str(), recv, args, guard, &binding); } } } @@ -103,7 +103,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: fn check_method_calls<'tcx>( cx: &LateContext<'tcx>, arm: &Arm<'tcx>, - method: Symbol, + method: &str, recv: &Expr<'_>, args: &[Expr<'_>], if_expr: &Expr<'_>, @@ -112,7 +112,7 @@ fn check_method_calls<'tcx>( let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let slice_like = ty.is_slice() || ty.is_array(); - let sugg = if method == sym!(is_empty) { + let sugg = if method == "is_empty" { // `s if s.is_empty()` becomes "" // `arr if arr.is_empty()` becomes [] @@ -137,9 +137,9 @@ fn check_method_calls<'tcx>( if needles.is_empty() { sugg.insert_str(1, ".."); - } else if method == sym!(starts_with) { + } else if method == "starts_with" { sugg.insert_str(sugg.len() - 1, ", .."); - } else if method == sym!(ends_with) { + } else if method == "ends_with" { sugg.insert_str(1, ".., "); } else { return; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 9ca75fb26155..ca45ed6ea59c 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -319,10 +319,9 @@ fn found_good_method<'tcx>( node: (&PatKind<'_>, &PatKind<'_>), ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { match node { - ( - PatKind::TupleStruct(ref path_left, patterns_left, _), - PatKind::TupleStruct(ref path_right, patterns_right, _), - ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _)) + if patterns_left.len() == 1 && patterns_right.len() == 1 => + { if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { find_good_method_for_match( cx, @@ -350,8 +349,8 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) - | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right)) + | (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _)) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { @@ -381,14 +380,14 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { get_good_method(cx, arms, path_left) } else { None } }, - (PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), + (PatKind::Path(path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), _ => None, } } diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 047d070a1315..2aea25b5f12a 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -247,7 +247,10 @@ impl<'a> PatState<'a> { let states = match self { Self::Wild => return None, Self::Other => { - *self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other))); + *self = Self::StdEnum( + cx.arena + .alloc_from_iter(std::iter::repeat_with(|| Self::Other).take(adt.variants().len())), + ); let Self::StdEnum(x) = self else { unreachable!(); }; diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 06482a743ddf..c1653b65e982 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -21,8 +21,8 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name, ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, - ExprKind::Closure(&Closure { body, .. }) => { - let body = cx.tcx.hir().body(body); + ExprKind::Closure(Closure { body, .. }) => { + let body = cx.tcx.hir().body(*body); let closure_expr = peel_blocks(body.value); match closure_expr.kind { ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => { @@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) { + if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" { Some(Self::IsSome { receiver, side_effect_expr_span, }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) { + } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" { Some(Self::IsOk { receiver, side_effect_expr_span, diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index cc82f6cfd638..a0c21faaa4cc 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{find_binding_init, path_to_local}; +use clippy_utils::macros::{is_assert_macro, root_macro_call}; +use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local}; use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; @@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_ if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) { return; } + if let Some(parent) = get_parent_expr(cx, expr) { + if let Some(parent) = get_parent_expr(cx, parent) { + if is_inside_always_const_context(cx.tcx, expr.hir_id) + && let Some(macro_call) = root_macro_call(parent.span) + && is_assert_macro(cx, macro_call.def_id) + { + return; + } + } + } let init_expr = expr_or_init(cx, receiver); if !receiver.span.eq_ctxt(init_expr.span) { return; diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 9d462bd18459..9a62b719a8fb 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -29,10 +29,7 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { // For array::IntoIter, the length is the second generic // parameter. - substs - .const_at(1) - .try_to_target_usize(cx.tcx) - .map(u128::from) + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) && let ExprKind::MethodCall(_, recv, ..) = iter.kind { diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints/src/methods/map_all_any_identity.rs new file mode 100644 index 000000000000..ac11baa2d54c --- /dev/null +++ b/clippy_lints/src/methods/map_all_any_identity.rs @@ -0,0 +1,43 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{is_expr_identity_function, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::{Span, sym}; + +use super::MAP_ALL_ANY_IDENTITY; + +#[allow(clippy::too_many_arguments)] +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + map_call_span: Span, + map_arg: &Expr<'_>, + any_call_span: Span, + any_arg: &Expr<'_>, + method: &str, +) { + if is_trait_method(cx, expr, sym::Iterator) + && is_trait_method(cx, recv, sym::Iterator) + && is_expr_identity_function(cx, any_arg) + && let map_any_call_span = map_call_span.with_hi(any_call_span.hi()) + && let Some(map_arg) = map_arg.span.get_source_text(cx) + { + span_lint_and_then( + cx, + MAP_ALL_ANY_IDENTITY, + map_any_call_span, + format!("usage of `.map(...).{method}(identity)`"), + |diag| { + diag.span_suggestion_verbose( + map_any_call_span, + format!("use `.{method}(...)` instead"), + format!("{method}({map_arg})"), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs new file mode 100644 index 000000000000..fc656fd78ba2 --- /dev/null +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -0,0 +1,134 @@ +use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; +use clippy_config::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eager_or_lazy, higher, usage}; +use rustc_ast::LitKind; +use rustc_ast::ast::RangeLimits; +use rustc_data_structures::packed::Pu128; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +fn extract_count_with_applicability( + cx: &LateContext<'_>, + range: higher::Range<'_>, + applicability: &mut Applicability, +) -> Option { + let start = range.start?; + let end = range.end?; + // TODO: This doens't handle if either the start or end are negative literals, or if the start is + // not a literal. In the first case, we need to be careful about how we handle computing the + // count to avoid overflows. In the second, we may need to add parenthesis to make the + // suggestion correct. + if let ExprKind::Lit(lit) = start.kind + && let LitKind::Int(Pu128(lower_bound), _) = lit.node + { + if let ExprKind::Lit(lit) = end.kind + && let LitKind::Int(Pu128(upper_bound), _) = lit.node + { + // Here we can explicitly calculate the number of iterations + let count = if upper_bound >= lower_bound { + match range.limits { + RangeLimits::HalfOpen => upper_bound - lower_bound, + RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?, + } + } else { + 0 + }; + return Some(format!("{count}")); + } + let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) + .maybe_par() + .into_string(); + if lower_bound == 0 { + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} + 1")); + } + return Some(end_snippet); + } + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} - {}", lower_bound - 1)); + } + return Some(format!("{end_snippet} - {lower_bound}")); + } + None +} + +pub(super) fn check( + cx: &LateContext<'_>, + ex: &Expr<'_>, + receiver: &Expr<'_>, + arg: &Expr<'_>, + msrv: &Msrv, + method_call_span: Span, +) { + let mut applicability = Applicability::MaybeIncorrect; + if let Some(range) = higher::Range::hir(receiver) + && let ExprKind::Closure(Closure { body, .. }) = arg.kind + && let body_hir = cx.tcx.hir().body(*body) + && let Body { + params: [param], + value: body_expr, + } = body_hir + && !usage::BindingUsageFinder::are_params_used(cx, body_hir) + && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) + { + let method_to_use_name; + let new_span; + let use_take; + + if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { + if msrv.meets(msrvs::REPEAT_N) { + method_to_use_name = "repeat_n"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, format!("{body_snippet}, {count}")); + use_take = false; + } else { + method_to_use_name = "repeat"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, body_snippet.to_string()); + use_take = true; + } + } else if msrv.meets(msrvs::REPEAT_WITH) { + method_to_use_name = "repeat_with"; + new_span = (param.span, String::new()); + use_take = true; + } else { + return; + } + + // We need to provide nonempty parts to diag.multipart_suggestion so we + // collate all our parts here and then remove those that are empty. + let mut parts = vec![ + ( + receiver.span.to(method_call_span), + format!("std::iter::{method_to_use_name}"), + ), + new_span, + ]; + if use_take { + parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); + } + + span_lint_and_then( + cx, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + ex.span, + "map of a closure that does not depend on its parameter over a range", + |diag| { + diag.multipart_suggestion( + if use_take { + format!("remove the explicit range and use `{method_to_use_name}` and `take`") + } else { + format!("remove the explicit range and use `{method_to_use_name}`") + }, + parts, + applicability, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 2a391870d70a..b1809796355d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -60,13 +60,16 @@ mod manual_ok_or; mod manual_saturating_arithmetic; mod manual_str_repeat; mod manual_try_fold; +mod map_all_any_identity; mod map_clone; mod map_collect_result_unit; mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod map_with_unused_argument_over_ranges; mod mut_mutex_lock; +mod needless_as_bytes; mod needless_character_iteration; mod needless_collect; mod needless_option_as_deref; @@ -4166,6 +4169,90 @@ declare_clippy_lint! { "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" } +declare_clippy_lint! { + /// ### What it does + /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. + /// + /// ### Why is this bad? + /// The `len()` and `is_empty()` methods are also directly available on strings, and they + /// return identical results. In particular, `len()` on a string returns the number of + /// bytes. + /// + /// ### Example + /// ``` + /// let len = "some string".as_bytes().len(); + /// let b = "some string".as_bytes().is_empty(); + /// ``` + /// Use instead: + /// ``` + /// let len = "some string".len(); + /// let b = "some string".is_empty(); + /// ``` + #[clippy::version = "1.84.0"] + pub NEEDLESS_AS_BYTES, + complexity, + "detect useless calls to `as_bytes()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. + /// + /// ### Why is this bad? + /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. + /// + /// ### Example + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); + /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); + /// ``` + /// Use instead: + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().all(|s| s.is_empty()); + /// let e2 = v.iter().any(|s| s.is_empty()); + /// ``` + #[clippy::version = "1.84.0"] + pub MAP_ALL_ANY_IDENTITY, + complexity, + "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::map` over ranges without using the parameter which + /// could be more clearly expressed using `std::iter::repeat(...).take(...)` + /// or `std::iter::repeat_n`. + /// + /// ### Why is this bad? + /// + /// It expresses the intent more clearly to `take` the correct number of times + /// from a generating function than to apply a closure to each number in a + /// range only to discard them. + /// + /// ### Example + /// + /// ```no_run + /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); + /// ``` + /// + /// ### Known Issues + /// + /// This lint may suggest replacing a `Map` with a `Take`. + /// The former implements some traits that the latter does not, such as + /// `DoubleEndedIterator`. + #[clippy::version = "1.84.0"] + pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + restriction, + "map of a trivial closure (not dependent on parameter) over a range" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [ NEEDLESS_CHARACTER_ITERATION, MANUAL_INSPECT, UNNECESSARY_MIN_OR_MAX, + NEEDLESS_AS_BYTES, + MAP_ALL_ANY_IDENTITY, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4534,15 +4624,21 @@ impl Methods { ("all", [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); - if let Some(("cloned", recv2, [], _, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ); + match method_call(recv) { + Some(("cloned", recv2, [], _, _)) => { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ); + }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all"); + }, + _ => {}, } }, ("and_then", [arg]) => { @@ -4571,6 +4667,9 @@ impl Methods { { string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any"); + }, _ => {}, } }, @@ -4764,8 +4863,14 @@ impl Methods { unit_hash::check(cx, expr, recv, arg); }, ("is_empty", []) => { - if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { - redundant_as_str::check(cx, expr, recv, as_str_span, span); + match method_call(recv) { + Some(("as_bytes", prev_recv, [], _, _)) => { + needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span); + }, + Some(("as_str", recv, [], as_str_span, _)) => { + redundant_as_str::check(cx, expr, recv, as_str_span, span); + }, + _ => {}, } is_empty::check(cx, expr, recv); }, @@ -4795,6 +4900,11 @@ impl Methods { ); } }, + ("len", []) => { + if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) { + needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span); + } + }, ("lock", []) => { mut_mutex_lock::check(cx, expr, recv, span); }, @@ -4802,6 +4912,7 @@ impl Methods { if name == "map" { unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, &self.msrv); + map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span); match method_call(recv) { Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); @@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase { } #[rustfmt::skip] -#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs new file mode 100644 index 000000000000..75e9f3172303 --- /dev/null +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -0,0 +1,28 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::is_type_lang_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, LangItem}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::NEEDLESS_AS_BYTES; + +pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) { + if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() + && let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs() + && (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str()) + { + let mut app = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + NEEDLESS_AS_BYTES, + span, + "needless call to `as_bytes()`", + format!("`{method}()` can be called directly on strings"), + format!("{sugg}.{method}()"), + app, + ); + } +} diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 96a31812ca47..055107068ae0 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { // Check function calls on our collection if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { if args.is_empty() - && method_name.ident.name == sym!(collect) + && method_name.ident.name.as_str() == "collect" && is_trait_method(self.cx, expr, sym::Iterator) { self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 65e545ed03a0..db2b9d4d92fb 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< if let Some(parent) = get_parent_expr(cx, expr) { let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind { if args.is_empty() - && segment.ident.name == sym!(parse) + && segment.ident.name.as_str() == "parse" && let parse_result_ty = cx.typeck_results().expr_ty(parent) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) && let ty::Adt(_, substs) = parse_result_ty.kind() @@ -58,7 +58,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< "calling `.parse()` on a string without trimming the trailing newline character", "checking", )) - } else if segment.ident.name == sym!(ends_with) + } else if segment.ident.name.as_str() == "ends_with" && recv.span == expr.span && let [arg] = args && expr_is_string_literal_without_trailing_newline(arg) diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index ca46da81fa05..bab439015c5f 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,13 +1,15 @@ use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_copy; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; +use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; use core::ops::ControlFlow; +use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; +use rustc_middle::query::Key; use rustc_middle::ty; use rustc_span::sym; @@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a ControlFlow::Continue(Descend::Yes) } }); - let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { + // Check if the closure is .filter_map(|x| Some(x)) + if name == "filter_map" + && let hir::ExprKind::Call(expr, args) = body.value.kind + && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome) + && arg_id.ty_def_id() == args[0].hir_id().ty_def_id() + && let hir::ExprKind::Path(_) = args[0].kind + { + span_lint_and_sugg( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + format!("{name} is unnecessary"), + "try removing the filter_map", + String::new(), + Applicability::MaybeIncorrect, + ); + } if name == "filter_map" { "map" } else { "map(..).next()" } } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(body.value).kind() { @@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a } else { return; }; - span_lint( + span_lint_and_sugg( cx, if name == "filter_map" { UNNECESSARY_FILTER_MAP @@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a UNNECESSARY_FIND_MAP }, expr.span, - format!("this `.{name}` can be written more simply using `.{sugg}`"), + format!("this `.{name}` can be written more simply"), + "try instead", + sugg.to_string(), + Applicability::MaybeIncorrect, ); } } @@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc (true, true) }, hir::ExprKind::MethodCall(segment, recv, [arg], _) => { - if segment.ident.name == sym!(then_some) + if segment.ident.name.as_str() == "then_some" && cx.typeck_results().expr_ty(recv).is_bool() && path_to_local_id(arg, arg_id) { diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index e95864c6db8b..ed89b3b34386 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -79,9 +79,9 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM }, ExprKind::MethodCall(path, receiver, args @ [_], _) => { if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { - if path.ident.name == sym!(max) { + if path.ident.name.as_str() == "max" { fetch_const(cx, Some(receiver), args, MinMax::Max) - } else if path.ident.name == sym!(min) { + } else if path.ident.name.as_str() == "min" { fetch_const(cx, Some(receiver), args, MinMax::Min) } else { None diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index ce508d85d634..1300c7d10628 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -12,11 +12,13 @@ use clippy_utils::is_from_proc_macro; use clippy_utils::source::SpanRangeExt; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; +use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Visibility; use rustc_session::impl_lint_pass; use rustc_span::def_id::CRATE_DEF_ID; +use rustc_span::symbol::kw; use rustc_span::{Span, sym}; declare_clippy_lint! { @@ -110,6 +112,21 @@ impl MissingDoc { return; } + if let Some(parent_def_id) = cx.tcx.opt_parent(def_id.to_def_id()) + && let DefKind::AnonConst + | DefKind::AssocConst + | DefKind::AssocFn + | DefKind::Closure + | DefKind::Const + | DefKind::Fn + | DefKind::InlineConst + | DefKind::Static { .. } + | DefKind::SyntheticCoroutineBody = cx.tcx.def_kind(parent_def_id) + { + // Nested item has no generated documentation, so it doesn't need to be documented. + return; + } + let has_doc = attrs .iter() .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())) @@ -184,8 +201,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { } } }, - hir::ItemKind::Const(..) - | hir::ItemKind::Enum(..) + hir::ItemKind::Const(..) => { + if it.ident.name == kw::Underscore { + note_prev_span_then_ret!(self.prev_span, it.span); + } + }, + hir::ItemKind::Enum(..) | hir::ItemKind::Macro(..) | hir::ItemKind::Mod(..) | hir::ItemKind::Static(..) diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index fc01b6753f1c..f28b431ab997 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -116,7 +116,7 @@ fn should_lint<'tcx>( if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) { has_debug_struct = true; - } else if path.ident.name == sym!(finish_non_exhaustive) + } else if path.ident.name.as_str() == "finish_non_exhaustive" && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct) { has_finish_non_exhaustive = true; diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index ce97370d4d9f..c48367729336 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -330,10 +330,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin } fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind + if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind && let Some(last_stmt) = loop_block.stmts.last() && let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind - && let ast::ExprKind::Continue(_) = inner_expr.kind + && let ast::ExprKind::Continue(continue_label) = inner_expr.kind + && compare_labels(loop_label.as_ref(), continue_label.as_ref()) { span_lint_and_help( cx, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 46cdb82130f9..a67addea9486 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -347,7 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { if let LetStmt { init: None, pat: - &Pat { + Pat { kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None), .. }, @@ -357,7 +357,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { && let Some((_, Node::Stmt(local_stmt))) = parents.next() && let Some((_, Node::Block(block))) = parents.next() { - check(cx, local, local_stmt, block, binding_id); + check(cx, local, local_stmt, block, *binding_id); } } } diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index 9a1c397b5b23..852a0ce8c6d5 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::def_id::{DefId, DefIdMap}; -use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, BoundPolarity, WherePredicate}; +use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicate}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ClauseKind, PredicatePolarity}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index f8c815d97295..b7dc269061cf 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -183,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { .iter() .zip(fn_sig.inputs()) .zip(body.params) - .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) + .filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) .peekable(); if it.peek().is_none() { return; diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 2e5195d459f4..74536028b5d4 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -159,8 +159,12 @@ impl NoEffect { // Remove `impl Future` to get `T` if cx.tcx.ty_is_opaque_future(ret_ty) - && let Some(true_ret_ty) = - cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty) + && let Some(true_ret_ty) = cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .get_impl_future_output_ty(ret_ty) { ret_ty = true_ret_ty; } diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 8d5a523fd8f6..64587e08ddc6 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -18,13 +18,13 @@ declare_clippy_lint! { /// Rust ABI can break this at any point. /// /// ### Example - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// fn example(arg_one: u32, arg_two: usize) {} /// ``` /// /// Use instead: - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// extern "C" fn example(arg_one: u32, arg_two: usize) {} /// ``` @@ -40,24 +40,25 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { if let ItemKind::Fn(fn_sig, _, _) = &item.kind { let attrs = cx.tcx.hir().attrs(item.hir_id()); let mut app = Applicability::MaybeIncorrect; - let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app); + let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(item.ident.span.lo()), "..", &mut app); for attr in attrs { if let Some(ident) = attr.ident() && ident.name == rustc_span::sym::no_mangle && fn_sig.header.abi == Abi::Rust - && let Some((fn_attrs, _)) = snippet.split_once("fn") + && let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn") && !fn_attrs.contains("extern") { let sugg_span = fn_sig .span .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) .shrink_to_lo(); + let attr_snippet = snippet(cx, attr.span, ".."); span_lint_and_then( cx, NO_MANGLE_WITH_RUST_ABI, fn_sig.span, - "`#[no_mangle]` set on a function with the default (`Rust`) ABI", + format!("`{attr_snippet}` set on a function with the default (`Rust`) ABI"), |diag| { diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 3f156aa55104..0caa19cd8443 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,12 +43,12 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym!(mode) + && ((path.ident.name.as_str() == "mode" && matches!( cx.tcx.get_diagnostic_name(adt.did()), Some(sym::FsOpenOptions | sym::DirBuilder) )) - || (path.ident.name == sym!(set_mode) + || (path.ident.name.as_str() == "set_mode" && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index 8e394944c213..ab5f91c1d672 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -107,7 +107,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind - && sym!(signum) == method_name.ident.name + && method_name.ident.name.as_str() == "signum" // Check that the receiver of the signum() is a float (expressions[0] is the receiver of // the method call) { diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 18e6aad9c9a0..794bef7b3214 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -34,7 +34,7 @@ declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]); impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: impl_items, .. }) = item.kind diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs index 56d07aeae175..808a7e005c67 100644 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ b/clippy_lints/src/ptr_offset_with_cast.rs @@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>( if path_segment.ident.name == sym::offset { return Some((arg_0, arg_1, Method::Offset)); } - if path_segment.ident.name == sym!(wrapping_offset) { + if path_segment.ident.name.as_str() == "wrapping_offset" { return Some((arg_0, arg_1, Method::WrappingOffset)); } } diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 9344cb41993a..cb374fcf20e3 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -93,7 +93,7 @@ enum IfBlockType<'hir> { fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> { if let Block { - stmts: &[], + stmts: [], expr: Some(els), .. } = block @@ -163,8 +163,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ is_type_diagnostic_item(cx, caller_ty, smbl) && expr_return_none_or_err(smbl, cx, if_then, caller, None) && match smbl { - sym::Option => call_sym == sym!(is_none), - sym::Result => call_sym == sym!(is_err), + sym::Option => call_sym.as_str() == "is_none", + sym::Result => call_sym.as_str() == "is_err", _ => false, } }, diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 19ce08bde10c..7d59bf24d937 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef { fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) { if let TyKind::Ref(_, ref mut_ty) = ty.kind && mut_ty.mutbl == Mutability::Not - && let TyKind::Path(ref qpath) = &mut_ty.ty.kind + && let TyKind::Path(qpath) = &mut_ty.ty.kind && let last = last_path_segment(qpath) && let Some(def_id) = last.res.opt_def_id() && cx.tcx.is_diagnostic_item(sym::Option, def_id) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 110dea8fb8eb..1e0f6dff1abe 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -347,7 +347,7 @@ fn check_final_expr<'tcx>( let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" - ExprKind::Ret(ref inner) => { + ExprKind::Ret(inner) => { // check if expr return nothing let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 09f1c1123529..d85f4a8fa016 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -101,17 +101,21 @@ impl SemicolonBlock { ); } - fn semicolon_outside_block( - &self, - cx: &LateContext<'_>, - block: &Block<'_>, - tail_stmt_expr: &Expr<'_>, - semi_span: Span, - ) { + fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) { let insert_span = block.span.with_lo(block.span.hi()); - // account for macro calls - let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span); - let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi()); + + // For macro call semicolon statements (`mac!();`), the statement's span does not actually + // include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon + // based on a source snippet. + // (Does not use `stmt_span` as that requires `.from_expansion()` to return true, + // which is not the case for e.g. `line!();` and `asm!();`) + let Some(remove_span) = cx + .sess() + .source_map() + .mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite()) + else { + return; + }; if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) { return; @@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock { }; let &Stmt { kind: StmtKind::Semi(expr), - span, .. } = stmt else { return; }; - self.semicolon_outside_block(cx, block, expr, span); + self.semicolon_outside_block(cx, block, expr); }, StmtKind::Semi(Expr { kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _), diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index fcf1c4cc5c27..6a0dfde2d9c9 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -26,7 +26,7 @@ declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]); impl<'tcx> LateLintPass<'tcx> for SerdeApi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items, .. }) = item.kind diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 9fdee8543a85..fa0824535042 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -174,7 +174,7 @@ impl SingleComponentPathImports { } match &item.kind { - ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { + ItemKind::Mod(_, ModKind::Loaded(items, ..)) => { self.check_mod(items); }, ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index ec6e45256d14..8c8f11569c8c 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -235,7 +235,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(extend) + && path.ident.name.as_str() == "extend" && self.is_repeat_take(extend_arg) { self.slow_expression = Some(InitializationType::Extend(expr)); @@ -247,7 +247,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(resize) + && path.ident.name.as_str() == "resize" // Check that is filled with 0 && is_integer_literal(fill_arg, 0) { @@ -269,7 +269,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { /// Returns `true` if give expression is `repeat(0).take(...)` fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind - && take_path.ident.name == sym!(take) + && take_path.ident.name.as_str() == "take" // Check that take is applied to `repeat(0)` && self.is_repeat_zero(recv) { diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index bf49bb601629..e09c07060065 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -286,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { if !in_external_macro(cx.sess(), e.span) && let ExprKind::MethodCall(path, receiver, ..) = &e.kind - && path.ident.name == sym!(as_bytes) + && path.ident.name.as_str() == "as_bytes" && let ExprKind::Lit(lit) = &receiver.kind && let LitKind::Str(lit_content, _) = &lit.node { @@ -332,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { } if let ExprKind::MethodCall(path, recv, [], _) = &e.kind - && path.ident.name == sym!(into_bytes) + && path.ident.name.as_str() == "into_bytes" && let ExprKind::MethodCall(path, recv, [], _) = &recv.kind && matches!(path.ident.name.as_str(), "to_owned" | "to_string") && let ExprKind::Lit(lit) = &recv.kind @@ -493,7 +493,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { let tyckres = cx.typeck_results(); if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind - && path.ident.name == sym!(split_whitespace) + && path.ident.name.as_str() == "split_whitespace" && let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id) && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index be32dc0aab0b..0d809c17989d 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -334,7 +334,7 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { let mut output = expr; loop { output = match &output.kind { - ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, + ExprKind::Paren(inner) | ExprKind::Unary(_, inner) => inner, _ => { return output; }, @@ -348,13 +348,13 @@ fn extract_related_binops(kind: &ExprKind) -> Option>> { fn if_statement_binops(kind: &ExprKind) -> Option>> { match kind { - ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), - ExprKind::Paren(ref e) => if_statement_binops(&e.kind), - ExprKind::Block(ref block, _) => { + ExprKind::If(condition, _, _) => chained_binops(&condition.kind), + ExprKind::Paren(e) => if_statement_binops(&e.kind), + ExprKind::Block(block, _) => { let mut output = None; for stmt in &block.stmts { - match stmt.kind { - StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { + match &stmt.kind { + StmtKind::Expr(e) | StmtKind::Semi(e) => { output = append_opt_vecs(output, if_statement_binops(&e.kind)); }, _ => {}, @@ -383,7 +383,7 @@ fn append_opt_vecs(target_opt: Option>, source_opt: Option>) -> fn chained_binops(kind: &ExprKind) -> Option>> { match kind { ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), - ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind), + ExprKind::Paren(e) | ExprKind::Unary(_, e) => chained_binops(&e.kind), _ => None, } } @@ -391,16 +391,14 @@ fn chained_binops(kind: &ExprKind) -> Option>> { fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option>> { match (&left_outer.kind, &right_outer.kind) { ( - ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), - ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), + ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), + ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e), ) => chained_binops_helper(left_e, right_e), - (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer), - (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { - chained_binops_helper(left_outer, right_e) - }, + (ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), _) => chained_binops_helper(left_e, right_outer), + (_, ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e)) => chained_binops_helper(left_outer, right_e), ( - ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right), - ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), + ExprKind::Binary(Spanned { node: left_op, .. }, left_left, left_right), + ExprKind::Binary(Spanned { node: right_op, .. }, right_left, right_right), ) => match ( chained_binops_helper(left_left, left_right), chained_binops_helper(right_left, right_right), diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 6cba560393d0..23362506ccad 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -392,7 +392,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> { for stmt in self.block.stmts { match stmt.kind { StmtKind::Expr(expr) | StmtKind::Semi(expr) => v.visit_expr(expr), - StmtKind::Let(LetStmt { ref init, .. }) => { + StmtKind::Let(LetStmt { init, .. }) => { if let Some(init) = init.as_ref() { v.visit_expr(init); } diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 3da4bf67558d..07bf4319ff0e 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -10,8 +10,8 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, - TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, BoundPolarity, + BoundPolarity, GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, + TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { let mut self_bounds_map = FxHashMap::default(); for predicate in item.generics.predicates { - if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate + if let WherePredicate::BoundPredicate(bound_predicate) = predicate && bound_predicate.origin != PredicateOrigin::ImplTrait && !bound_predicate.span.from_expansion() && let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind @@ -268,7 +268,7 @@ impl TraitBounds { let mut map: UnhashMap, Vec<&GenericBound<'_>>> = UnhashMap::default(); let mut applicability = Applicability::MaybeIncorrect; for bound in generics.predicates { - if let WherePredicate::BoundPredicate(ref p) = bound + if let WherePredicate::BoundPredicate(p) = bound && p.origin != PredicateOrigin::ImplTrait && p.bounds.len() as u64 <= self.max_trait_bounds && !p.span.from_expansion() @@ -379,7 +379,7 @@ struct ComparableTraitRef<'a, 'tcx> { impl PartialEq for ComparableTraitRef<'_, '_> { fn eq(&self, other: &Self) -> bool { - SpanlessEq::new(self.cx).eq_modifiers(self.modifiers, other.modifiers) + SpanlessEq::eq_modifiers(self.modifiers, other.modifiers) && SpanlessEq::new(self.cx) .paths_by_resolution() .eq_path(self.trait_ref.path, other.trait_ref.path) diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 1dfc9f7091e8..ca9daf2d2a03 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -43,7 +43,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_ binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs) }, ExprKind::MethodCall(path, receiver, [arg], _) - if path.ident.name == sym!(contains) + if path.ident.name.as_str() == "contains" // ... `contains` called on some kind of range && let Some(receiver_adt) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() && let lang_items = cx.tcx.lang_items() @@ -81,7 +81,7 @@ pub(super) fn check<'tcx>( if let Some(then_some_call) = peel_parent_unsafe_blocks(cx, expr) && let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind && cx.typeck_results().expr_ty(receiver).is_bool() - && path.ident.name == sym!(then_some) + && path.ident.name.as_str() == "then_some" && is_local_with_projections(transmutable) && binops_with_local(cx, transmutable, receiver) && is_normalizable(cx, cx.param_env, from_ty) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 44eb0a6c593d..0bba611116bb 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -295,7 +295,7 @@ fn expr_has_unnecessary_safety_comment<'tcx>( if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| { matches!( node, - Node::Block(&Block { + Node::Block(Block { rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), .. }), diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index bf2d75ea1a96..1a1284ce9c43 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -194,7 +194,7 @@ fn needs_inferred_result_ty( let (id, receiver, args) = match e.kind { ExprKind::Call( Expr { - kind: ExprKind::Path(ref path), + kind: ExprKind::Path(path), hir_id, .. }, diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index e7d26fa238e5..0f4bd286145a 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -149,7 +149,7 @@ fn is_empty_block(expr: &Expr<'_>) -> bool { expr.kind, ExprKind::Block( Block { - stmts: &[], + stmts: [], expr: None, .. }, diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index cf406b817da4..7c9455bf8abe 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks}; +use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -83,6 +83,28 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { /// to consider the arms, and we want to avoid breaking the logic for situations where things /// get desugared to match. fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { + let fn_def_id = block.hir_id.owner.to_def_id(); + if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id) + && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id) + { + // We don't want to lint inside io::Read or io::Write implementations, as the author has more + // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 + if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + return; + } + + let async_paths: [&[&str]; 4] = [ + &paths::TOKIO_IO_ASYNCREADEXT, + &paths::TOKIO_IO_ASYNCWRITEEXT, + &paths::FUTURES_IO_ASYNCREADEXT, + &paths::FUTURES_IO_ASYNCWRITEEXT, + ]; + + if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) { + return; + } + } + for stmt in block.stmts { if let hir::StmtKind::Semi(exp) = stmt.kind { check_expr(cx, exp); @@ -222,7 +244,7 @@ fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { } fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - while let ExprKind::Call(func, [ref arg_0]) = expr.kind + while let ExprKind::Call(func, [arg_0]) = expr.kind && matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..)) @@ -244,7 +266,7 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { /// waited on. Otherwise return None. fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind { - if let ExprKind::Call(func, [ref arg_0]) = expr.kind { + if let ExprKind::Call(func, [arg_0]) = expr.kind { if matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) diff --git a/clippy_lints/src/utils/attr_collector.rs b/clippy_lints/src/utils/attr_collector.rs new file mode 100644 index 000000000000..1522553bbf52 --- /dev/null +++ b/clippy_lints/src/utils/attr_collector.rs @@ -0,0 +1,40 @@ +use std::mem; +use std::sync::OnceLock; + +use rustc_ast::{Attribute, Crate}; +use rustc_data_structures::sync::Lrc; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::Span; + +#[derive(Clone, Default)] +pub struct AttrStorage(pub Lrc>>); + +pub struct AttrCollector { + storage: AttrStorage, + attrs: Vec, +} + +impl AttrCollector { + pub fn new(storage: AttrStorage) -> Self { + Self { + storage, + attrs: Vec::new(), + } + } +} + +impl_lint_pass!(AttrCollector => []); + +impl EarlyLintPass for AttrCollector { + fn check_attribute(&mut self, _cx: &EarlyContext<'_>, attr: &Attribute) { + self.attrs.push(attr.span); + } + + fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) { + self.storage + .0 + .set(mem::take(&mut self.attrs)) + .expect("should only be called once"); + } +} diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 31f9d84f5e46..1bf24083665f 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -396,7 +396,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.pat(field!(let_expr.pat)); // Does what ExprKind::Cast does, only adds a clause for the type // if it's a path - if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { + if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { bind!(self, qpath); chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind"); self.qpath(qpath); diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index f662c7651f6b..deb983b6971d 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -6,5 +6,6 @@ pub mod lint_without_lint_pass; pub mod msrv_attr_impl; pub mod outer_expn_data_pass; pub mod produce_ice; +pub mod slow_symbol_comparisons; pub mod unnecessary_def_path; pub mod unsorted_clippy_utils_paths; diff --git a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs index a3f9abe4f961..eaeb754a23f1 100644 --- a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs +++ b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind && is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]) - && let ExprKind::Closure(&Closure { body, .. }) = &call_f.kind + && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind && let body = cx.tcx.hir().body(body) && let only_expr = peel_blocks_with_stmt(body.value) && let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind diff --git a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs index 51235de9f29f..af38e066559c 100644 --- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not + /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not /// know the name of the lint. /// /// ### Known problems @@ -123,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { .expect("lints must have a description field"); if let ExprKind::Lit(Spanned { - node: LitKind::Str(ref sym, _), + node: LitKind::Str(sym, _), .. }) = field.expr.kind { @@ -159,8 +159,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { let body = cx.tcx.hir().body_owned_by( impl_item_refs .iter() - .find(|iiref| iiref.ident.as_str() == "get_lints") - .expect("LintPass needs to implement get_lints") + .find(|iiref| iiref.ident.as_str() == "lint_vec") + .expect("LintPass needs to implement lint_vec") .id .owner_id .def_id, @@ -218,9 +218,7 @@ pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) { if let Some(value) = extract_clippy_version_value(cx, item) { - // The `sym!` macro doesn't work as it only expects a single token. - // It's better to keep it this way and have a direct `Symbol::intern` call here. - if value == Symbol::intern("pre 1.29.0") { + if value.as_str() == "pre 1.29.0" { return; } @@ -251,7 +249,7 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<' pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option { let attrs = cx.tcx.hir().attrs(item.hir_id()); attrs.iter().find_map(|attr| { - if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind + if let ast::AttrKind::Normal(attr_kind) = &attr.kind // Identify attribute && let [tool_name, attr_name] = &attr_kind.item.path.segments[..] && tool_name.ident.name == sym::clippy diff --git a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs index 63fcbd61528d..686922461530 100644 --- a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs +++ b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs @@ -42,7 +42,7 @@ impl LateLintPass<'_> for MsrvAttrImpl { .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_))) .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV)) }) - && !items.iter().any(|item| item.ident.name == sym!(check_attributes)) + && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") { let context = if is_late_pass { "LateContext" } else { "EarlyContext" }; let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" }; diff --git a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs new file mode 100644 index 000000000000..3742be0e103d --- /dev/null +++ b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs @@ -0,0 +1,69 @@ +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::match_type; +use clippy_utils::{match_function_call, paths}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// + /// Detects symbol comparision using `Symbol::intern`. + /// + /// ### Why is this bad? + /// + /// Comparision via `Symbol::as_str()` is faster if the interned symbols are not reused. + /// + /// ### Example + /// + /// None, see suggestion. + pub SLOW_SYMBOL_COMPARISONS, + internal, + "detects slow comparisions of symbol" +} + +declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]); + +fn check_slow_comparison<'tcx>( + cx: &LateContext<'tcx>, + op1: &'tcx Expr<'tcx>, + op2: &'tcx Expr<'tcx>, +) -> Option<(Span, String)> { + if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL) + && let Some([symbol_name_expr]) = match_function_call(cx, op2, &paths::SYMBOL_INTERN) + && let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr) + { + Some((op1.span, symbol_name)) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for SlowSymbolComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Binary(op, left, right) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + && let Some((symbol_span, symbol_name)) = + check_slow_comparison(cx, left, right).or_else(|| check_slow_comparison(cx, right, left)) + { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + SLOW_SYMBOL_COMPARISONS, + expr.span, + "comparing `Symbol` via `Symbol::intern`", + "use `Symbol::as_str` and check the string instead", + format!( + "{}.as_str() {} \"{symbol_name}\"", + snippet_with_applicability(cx, symbol_span, "symbol", &mut applicability), + op.node.as_str() + ), + applicability, + ); + }; + } +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 13e9ead9a57f..4476cd1005e7 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1,5 +1,7 @@ +pub mod attr_collector; pub mod author; pub mod dump_hir; pub mod format_args_collector; + #[cfg(feature = "internal")] pub mod internal_lints; diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index c5e2c8c09a27..75169e05734a 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -21,7 +21,7 @@ use rustc_hir::{ ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, }; -use rustc_lint::{LateContext, LintContext, EarlyContext}; +use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::symbol::{Ident, kw}; @@ -63,8 +63,8 @@ fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit), } && match end_pat { Pat::Str(text) => end_str.ends_with(text), - Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)), - Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)), + Pat::MultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)), + Pat::OwnedMultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)), Pat::Sym(sym) => end_str.ends_with(sym.as_str()), Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit), }) @@ -333,26 +333,32 @@ fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) { match attr.kind { AttrKind::Normal(..) => { if let Some(ident) = attr.ident() { - // TODO: I feel like it's likely we can use `Cow` instead but this will require quite a bit of - // refactoring // NOTE: This will likely have false positives, like `allow = 1` - ( - Pat::OwnedMultiStr(vec![ident.to_string(), "#".to_owned()]), - Pat::Str(""), - ) + let ident_string = ident.to_string(); + if attr.style == AttrStyle::Outer { + ( + Pat::OwnedMultiStr(vec!["#[".to_owned() + &ident_string, ident_string]), + Pat::Str(""), + ) + } else { + ( + Pat::OwnedMultiStr(vec!["#![".to_owned() + &ident_string, ident_string]), + Pat::Str(""), + ) + } } else { (Pat::Str("#"), Pat::Str("]")) } }, AttrKind::DocComment(_kind @ CommentKind::Line, ..) => { - if matches!(attr.style, AttrStyle::Outer) { + if attr.style == AttrStyle::Outer { (Pat::Str("///"), Pat::Str("")) } else { (Pat::Str("//!"), Pat::Str("")) } }, AttrKind::DocComment(_kind @ CommentKind::Block, ..) => { - if matches!(attr.style, AttrStyle::Outer) { + if attr.style == AttrStyle::Outer { (Pat::Str("/**"), Pat::Str("*/")) } else { (Pat::Str("/*!"), Pat::Str("*/")) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 67c31abbddaa..24a02c7ef871 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1,3 +1,7 @@ +//! A simple const eval API, for use on arbitrary HIR expressions. +//! +//! This cannot use rustc's const eval, aka miri, as arbitrary HIR expressions cannot be lowered to +//! executable MIR bodies, so we have to do this instead. #![allow(clippy::float_cmp)] use crate::macros::HirNode; @@ -379,6 +383,8 @@ impl Ord for FullInt { /// The context required to evaluate a constant expression. /// /// This is currently limited to constant folding and reading the value of named constants. +/// +/// See the module level documentation for some context. pub struct ConstEvalCtxt<'tcx> { tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index 4877fb65d37d..993035001c1b 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -8,7 +8,9 @@ //! Thank you! //! ~The `INTERNAL_METADATA_COLLECTOR` lint -use rustc_errors::{Applicability, Diag, DiagMessage, MultiSpan, SubdiagMessage}; +use rustc_errors::{ + Applicability, Diag, DiagMessage, EmissionGuarantee, MultiSpan, SubdiagMessage, SubstitutionPart, Suggestions, +}; use rustc_hir::HirId; use rustc_lint::{LateContext, Lint, LintContext}; use rustc_span::Span; @@ -28,6 +30,42 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { } } +/// Makes sure that a diagnostic is well formed. +/// +/// rustc debug asserts a few properties about spans, +/// but the clippy repo uses a distributed rustc build with debug assertions disabled, +/// so this has historically led to problems during subtree syncs where those debug assertions +/// only started triggered there. +/// +/// This function makes sure we also validate them in debug clippy builds. +fn validate_diag(diag: &Diag<'_, impl EmissionGuarantee>) { + let suggestions = match &diag.suggestions { + Suggestions::Enabled(suggs) => &**suggs, + Suggestions::Sealed(suggs) => &**suggs, + Suggestions::Disabled => return, + }; + + for substitution in suggestions.iter().flat_map(|s| &s.substitutions) { + assert_eq!( + substitution + .parts + .iter() + .find(|SubstitutionPart { snippet, span }| snippet.is_empty() && span.is_empty()), + None, + "span must not be empty and have no suggestion" + ); + + assert_eq!( + substitution + .parts + .array_windows() + .find(|[a, b]| a.span.overlaps(b.span)), + None, + "suggestion must not have overlapping parts" + ); + } +} + /// Emit a basic lint message with a `msg` and a `span`. /// /// This is the most primitive of our lint emission methods and can @@ -64,6 +102,9 @@ pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into( diag.help(help.into()); } docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -175,6 +219,9 @@ pub fn span_lint_and_note( diag.note(note.into()); } docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -208,6 +255,9 @@ where diag.primary_message(msg); f(diag); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -240,6 +290,9 @@ pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, s cx.tcx.node_span_lint(lint, hir_id, sp, |diag| { diag.primary_message(msg); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -280,6 +333,9 @@ pub fn span_lint_hir_and_then( diag.primary_message(msg); f(diag); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -316,7 +372,7 @@ pub fn span_lint_hir_and_then( /// | /// = note: `-D fold-any` implied by `-D warnings` /// ``` -#[expect(clippy::collapsible_span_lint_calls)] +#[cfg_attr(not(debug_assertions), expect(clippy::collapsible_span_lint_calls))] pub fn span_lint_and_sugg( cx: &T, lint: &'static Lint, @@ -328,5 +384,8 @@ pub fn span_lint_and_sugg( ) { span_lint_and_then(cx, lint, sp, msg.into(), |diag| { diag.span_suggestion(sp, help.into(), sugg, applicability); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 3175a9a1dd31..11bbe734844d 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -103,7 +103,7 @@ impl<'hir> IfLet<'hir> { /// Parses an `if let` expression pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option { if let ExprKind::If( - Expr { + &Expr { kind: ExprKind::Let(&hir::LetExpr { pat: let_pat, @@ -381,12 +381,12 @@ impl<'hir> WhileLet<'hir> { /// Parses a desugared `while let` loop pub const fn hir(expr: &Expr<'hir>) -> Option { if let ExprKind::Loop( - Block { + &Block { expr: - Some(Expr { + Some(&Expr { kind: ExprKind::If( - Expr { + &Expr { kind: ExprKind::Let(&hir::LetExpr { pat: let_pat, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 8004bc68b2ea..cb69f8e5a0ed 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -128,7 +128,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { self.inter_expr().eq_path_segments(left, right) } - pub fn eq_modifiers(&mut self, left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool { + pub fn eq_modifiers(left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool { std::mem::discriminant(&left.constness) == std::mem::discriminant(&right.constness) && std::mem::discriminant(&left.polarity) == std::mem::discriminant(&right.polarity) } @@ -1201,11 +1201,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); self.hash_pat(pat); }, - TyKind::Ptr(ref mut_ty) => { + TyKind::Ptr(mut_ty) => { self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); }, - TyKind::Ref(lifetime, ref mut_ty) => { + TyKind::Ref(lifetime, mut_ty) => { self.hash_lifetime(lifetime); self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); @@ -1230,14 +1230,19 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); } }, - TyKind::Path(ref qpath) => self.hash_qpath(qpath), + TyKind::Path(qpath) => self.hash_qpath(qpath), TyKind::TraitObject(_, lifetime, _) => { self.hash_lifetime(lifetime); }, TyKind::Typeof(anon_const) => { self.hash_body(anon_const.body); }, - TyKind::Err(_) | TyKind::Infer | TyKind::Never | TyKind::InferDelegation(..) | TyKind::OpaqueDef(_) | TyKind::AnonAdt(_) => {}, + TyKind::Err(_) + | TyKind::Infer + | TyKind::Never + | TyKind::InferDelegation(..) + | TyKind::OpaqueDef(_) + | TyKind::AnonAdt(_) => {}, } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0f712068e658..50d334acf18e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -9,6 +9,7 @@ #![feature(rustc_private)] #![feature(assert_matches)] #![feature(unwrap_infallible)] +#![feature(array_windows)] #![recursion_limit = "512"] #![allow( clippy::missing_errors_doc, @@ -688,11 +689,11 @@ pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> Vec { /// /// This function is expensive and should be used sparingly. pub fn def_path_res(tcx: TyCtxt<'_>, path: &[&str]) -> Vec { - let (base, path) = match *path { + let (base, path) = match path { [primitive] => { return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)]; }, - [base, ref path @ ..] => (base, path), + [base, path @ ..] => (base, path), _ => return Vec::new(), }; @@ -744,7 +745,7 @@ pub fn def_path_res_with_base(tcx: TyCtxt<'_>, mut base: Vec, mut path: &[& } /// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`]. -pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator { +pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator + use<> { def_path_res(tcx, path).into_iter().filter_map(|res| res.opt_def_id()) } @@ -934,7 +935,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { } }, ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func), - ExprKind::Call(from_func, [ref arg]) => is_default_equivalent_from(cx, from_func, arg), + ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg), ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone), ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), _ => false, @@ -947,7 +948,7 @@ fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: & { match arg.kind { ExprKind::Lit(hir::Lit { - node: LitKind::Str(ref sym, _), + node: LitKind::Str(sym, _), .. }) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String), ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec), @@ -1337,15 +1338,17 @@ pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { pub struct ContainsName<'a, 'tcx> { pub cx: &'a LateContext<'tcx>, pub name: Symbol, - pub result: bool, } impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; - fn visit_name(&mut self, name: Symbol) { + fn visit_name(&mut self, name: Symbol) -> Self::Result { if self.name == name { - self.result = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } @@ -1356,13 +1359,8 @@ impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> { /// Checks if an `Expr` contains a certain name. pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { - let mut cn = ContainsName { - name, - result: false, - cx, - }; - cn.visit_expr(expr); - cn.result + let mut cn = ContainsName { cx, name }; + cn.visit_expr(expr).is_break() } /// Returns `true` if `expr` contains a return expression @@ -3459,7 +3457,7 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { matches!( cx.tcx.parent_hir_node(id), - Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) + Node::Stmt(..) | Node::Block(Block { stmts: [], .. }) ) } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 41785e161d0c..ce1a20e0066d 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -19,7 +19,7 @@ use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, TypingMode, + TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; @@ -274,11 +274,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( .map(|arg| arg.into().unwrap_or_else(|| infcx.next_ty_var(DUMMY_SP).into())) .collect::>(); - let trait_ref = TraitRef::new( - tcx, - trait_id, - [GenericArg::from(ty)].into_iter().chain(args), - ); + let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)].into_iter().chain(args)); debug_assert_matches!( tcx.def_kind(trait_id), @@ -975,9 +971,7 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) { (Ok(size), _) => size, (Err(_), ty::Tuple(list)) => list.iter().map(|t| approx_ty_size(cx, t)).sum(), - (Err(_), ty::Array(t, n)) => { - n.try_to_target_usize(cx.tcx).unwrap_or_default() * approx_ty_size(cx, *t) - }, + (Err(_), ty::Array(t, n)) => n.try_to_target_usize(cx.tcx).unwrap_or_default() * approx_ty_size(cx, *t), (Err(_), ty::Adt(def, subst)) if def.is_struct() => def .variants() .iter() @@ -1284,7 +1278,12 @@ pub fn make_normalized_projection_with_regions<'tcx>( pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { let cause = ObligationCause::dummy(); - match tcx.infer_ctxt().build(TypingMode::from_param_env(param_env)).at(&cause, param_env).query_normalize(ty) { + match tcx + .infer_ctxt() + .build(TypingMode::from_param_env(param_env)) + .at(&cause, param_env) + .query_normalize(ty) + { Ok(ty) => ty.value, Err(_) => ty, } diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 3021f21df121..d7640ebfb006 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -302,9 +302,9 @@ fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bo // Check that all type parameters appear in the functions input types. (0..(generics.parent_count + generics.own_params.len()) as u32).all(|index| { fn_sig - .inputs() - .iter() - .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) + .inputs() + .iter() + .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) }) } diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 4bfc9dd92135..c8c25456f696 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -109,34 +109,28 @@ impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector { pub struct BindingUsageFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, binding_ids: Vec, - usage_found: bool, } impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> { pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool { let mut finder = BindingUsageFinder { cx, binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body), - usage_found: false, }; - finder.visit_body(body); - finder.usage_found + finder.visit_body(body).is_break() } } impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if !self.usage_found { - intravisit::walk_expr(self, expr); - } - } - - fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) { + fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result { if let Res::Local(id) = path.res { if self.binding_ids.contains(&id) { - self.usage_found = true; + return ControlFlow::Break(()); } } + + ControlFlow::Continue(()) } fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 8db6502dbfb2..8f5ec185bf15 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -324,17 +324,15 @@ pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tc pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_const: bool, } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = intravisit::nested_filter::None; - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if !self.is_const { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { match e.kind { - ExprKind::ConstBlock(_) => return, + ExprKind::ConstBlock(_) => return ControlFlow::Continue(()), ExprKind::Call( &Expr { kind: ExprKind::Path(ref p), @@ -394,37 +392,34 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> | ExprKind::Type(..) => (), _ => { - self.is_const = false; - return; + return ControlFlow::Break(()); }, } - walk_expr(self, e); + + walk_expr(self, e) } } - let mut v = V { cx, is_const: true }; - v.visit_expr(e); - v.is_const + let mut v = V { cx }; + v.visit_expr(e).is_continue() } /// Checks if the given expression performs an unsafe operation outside of an unsafe block. pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_unsafe: bool, } impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; + type Result = ControlFlow<()>; + fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.is_unsafe { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { match e.kind { ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => { - self.is_unsafe = true; + ControlFlow::Break(()) }, ExprKind::MethodCall(..) if self @@ -435,13 +430,13 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe }) => { - self.is_unsafe = true; + ControlFlow::Break(()) }, ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() { ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe => { - self.is_unsafe = true; + ControlFlow::Break(()) }, - ty::FnPtr(_, hdr) if hdr.safety == Safety::Unsafe => self.is_unsafe = true, + ty::FnPtr(_, hdr) if hdr.safety == Safety::Unsafe => ControlFlow::Break(()), _ => walk_expr(self, e), }, ExprKind::Path(ref p) @@ -451,56 +446,54 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { .opt_def_id() .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) => { - self.is_unsafe = true; + ControlFlow::Break(()) }, _ => walk_expr(self, e), } } - fn visit_block(&mut self, b: &'tcx Block<'_>) { - if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) { - walk_block(self, b); + fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result { + if matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) { + ControlFlow::Continue(()) + } else { + walk_block(self, b) } } - fn visit_nested_item(&mut self, id: ItemId) { - if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind { - self.is_unsafe = i.safety == Safety::Unsafe; + fn visit_nested_item(&mut self, id: ItemId) -> Self::Result { + if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind + && i.safety == Safety::Unsafe + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } } - let mut v = V { cx, is_unsafe: false }; - v.visit_expr(e); - v.is_unsafe + let mut v = V { cx }; + v.visit_expr(e).is_break() } /// Checks if the given expression contains an unsafe block pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { struct V<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, - found_unsafe: bool, } impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_block(&mut self, b: &'tcx Block<'_>) { - if self.found_unsafe { - return; - } + fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result { if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - self.found_unsafe = true; - return; + ControlFlow::Break(()) + } else { + walk_block(self, b) } - walk_block(self, b); } } - let mut v = V { - cx, - found_unsafe: false, - }; - v.visit_expr(e); - v.found_unsafe + let mut v = V { cx }; + v.visit_expr(e).is_break() } /// Runs the given function for each sub-expression producing the final value consumed by the parent diff --git a/rust-toolchain b/rust-toolchain index e11ab40b9de3..37d9cce24650 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2024-10-18" +channel = "nightly-2024-11-07" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 5774e20e0be5..b8e0413e97bc 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -396,7 +396,8 @@ struct Renderer<'a> { impl Renderer<'_> { fn markdown(input: &str) -> Safe { - let parser = Parser::new_ext(input, Options::all()); + let input = clippy_config::sanitize_explanation(input); + let parser = Parser::new_ext(&input, Options::all()); let mut html_output = String::new(); html::push_html(&mut html_output, parser); // Oh deer, what a hack :O diff --git a/tests/ui-internal/lint_without_lint_pass.rs b/tests/ui-internal/lint_without_lint_pass.rs index 1fd03cfe36da..d59e9cbbb617 100644 --- a/tests/ui-internal/lint_without_lint_pass.rs +++ b/tests/ui-internal/lint_without_lint_pass.rs @@ -7,7 +7,7 @@ extern crate rustc_middle; #[macro_use] extern crate rustc_session; extern crate rustc_lint; -use rustc_lint::LintPass; +use rustc_lint::{LintPass, LintVec}; declare_tool_lint! { pub clippy::TEST_LINT, @@ -35,6 +35,9 @@ impl LintPass for Pass { fn name(&self) -> &'static str { "TEST_LINT" } + fn get_lints(&self) -> LintVec { + vec![TEST_LINT] + } } declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); diff --git a/tests/ui-internal/slow_symbol_comparisons.fixed b/tests/ui-internal/slow_symbol_comparisons.fixed new file mode 100644 index 000000000000..2cbd646a0fd5 --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.fixed @@ -0,0 +1,24 @@ +#![feature(rustc_private)] +#![warn(clippy::slow_symbol_comparisons)] + +extern crate rustc_span; + +use clippy_utils::sym; +use rustc_span::Symbol; + +fn main() { + let symbol = sym!(example); + let other_symbol = sym!(other_example); + + // Should lint + let slow_comparison = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_macro = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_backwards = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + + // Should not lint + let faster_comparison = symbol.as_str() == "other_example"; + let preinterned_comparison = symbol == other_symbol; +} diff --git a/tests/ui-internal/slow_symbol_comparisons.rs b/tests/ui-internal/slow_symbol_comparisons.rs new file mode 100644 index 000000000000..0cea3c3fcff9 --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.rs @@ -0,0 +1,24 @@ +#![feature(rustc_private)] +#![warn(clippy::slow_symbol_comparisons)] + +extern crate rustc_span; + +use clippy_utils::sym; +use rustc_span::Symbol; + +fn main() { + let symbol = sym!(example); + let other_symbol = sym!(other_example); + + // Should lint + let slow_comparison = symbol == Symbol::intern("example"); + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_macro = symbol == sym!(example); + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_backwards = sym!(example) == symbol; + //~^ error: comparing `Symbol` via `Symbol::intern` + + // Should not lint + let faster_comparison = symbol.as_str() == "other_example"; + let preinterned_comparison = symbol == other_symbol; +} diff --git a/tests/ui-internal/slow_symbol_comparisons.stderr b/tests/ui-internal/slow_symbol_comparisons.stderr new file mode 100644 index 000000000000..72cb20a7fed9 --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.stderr @@ -0,0 +1,23 @@ +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:14:27 + | +LL | let slow_comparison = symbol == Symbol::intern("example"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + | + = note: `-D clippy::slow-symbol-comparisons` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::slow_symbol_comparisons)]` + +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:16:33 + | +LL | let slow_comparison_macro = symbol == sym!(example); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:18:37 + | +LL | let slow_comparison_backwards = sym!(example) == symbol; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + +error: aborting due to 3 previous errors + diff --git a/tests/ui-internal/unnecessary_symbol_str.fixed b/tests/ui-internal/unnecessary_symbol_str.fixed index eb79fdbc4b4c..8e7f020c1f65 100644 --- a/tests/ui-internal/unnecessary_symbol_str.fixed +++ b/tests/ui-internal/unnecessary_symbol_str.fixed @@ -1,6 +1,7 @@ #![feature(rustc_private)] #![deny(clippy::internal)] #![allow( + clippy::slow_symbol_comparisons, clippy::borrow_deref_ref, clippy::unnecessary_operation, unused_must_use, diff --git a/tests/ui-internal/unnecessary_symbol_str.rs b/tests/ui-internal/unnecessary_symbol_str.rs index bbea13af92a5..9aeeb9aaf3aa 100644 --- a/tests/ui-internal/unnecessary_symbol_str.rs +++ b/tests/ui-internal/unnecessary_symbol_str.rs @@ -1,6 +1,7 @@ #![feature(rustc_private)] #![deny(clippy::internal)] #![allow( + clippy::slow_symbol_comparisons, clippy::borrow_deref_ref, clippy::unnecessary_operation, unused_must_use, diff --git a/tests/ui-internal/unnecessary_symbol_str.stderr b/tests/ui-internal/unnecessary_symbol_str.stderr index 551167a9ff58..668c11722f91 100644 --- a/tests/ui-internal/unnecessary_symbol_str.stderr +++ b/tests/ui-internal/unnecessary_symbol_str.stderr @@ -1,5 +1,5 @@ error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:15:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:16:5 | LL | Symbol::intern("foo").as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::sym::clippy` @@ -12,25 +12,25 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::unnecessary_symbol_str)]` implied by `#[deny(clippy::internal)]` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:16:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:17:5 | LL | Symbol::intern("foo").to_string() == "self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:17:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:18:5 | LL | Symbol::intern("foo").to_ident_string() != "Self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:18:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:19:5 | LL | &*Ident::empty().as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:19:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:20:5 | LL | "clippy" == Ident::empty().to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name` diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml new file mode 100644 index 000000000000..fbdb0c9c37d0 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["fn", "type", "const", "type"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml new file mode 100644 index 000000000000..720655e5cce1 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["const", "type"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml new file mode 100644 index 000000000000..0dd5407be10b --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["enum", "impl", "module", "struct", "trait", "struct"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml new file mode 100644 index 000000000000..ddca5cfa5779 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml @@ -0,0 +1,12 @@ +trait-assoc-item-kinds-order = ["const", "type", "fn"] +source-item-ordering = ["enum", "impl", "module", "struct", "trait"] +module-item-order-groupings = [ + ["modules", ["extern_crate", "mod", "foreign_mod"]], + ["use", ["use"]], + ["macros", ["macro"]], + ["global_asm", ["global_asm"]], + ["UPPER_SNAKE_CASE", ["static", "const"]], + ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], + ["lower_snake_case", ["fn"]] +] + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml new file mode 100644 index 000000000000..2144bdc9a0ce --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["enum"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml new file mode 100644 index 000000000000..54b6727fabf7 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["impl"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml new file mode 100644 index 000000000000..b551611c35e4 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["trait"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr new file mode 100644 index 000000000000..e441c7c1241c --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. The trait associated item kinds are: [Const, Fn, Type] + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml:1:32 + | +LL | trait-assoc-item-kinds-order = ["fn", "type", "const", "type"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr new file mode 100644 index 000000000000..183f0b033191 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. The trait associated item kinds are: [Const, Fn, Type] + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml:1:32 + | +LL | trait-assoc-item-kinds-order = ["const", "type"] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr new file mode 100644 index 000000000000..abf58dbd1101 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: The category "Struct" was enabled more than once in the source ordering configuration. + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml:1:24 + | +LL | source-item-ordering = ["enum", "impl", "module", "struct", "trait", "struct"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs new file mode 100644 index 000000000000..e9bcc30c15e0 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs @@ -0,0 +1,186 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default default_exp bad_conf_1 bad_conf_2 bad_conf_3 +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default +//@[default_exp] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default_exp +//@[bad_conf_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1 +//@[bad_conf_2] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2 +//@[bad_conf_3] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + const A: bool = false; + const B: bool = false; + const C: bool = false; + + type SomeType = (); + + fn a() {} + fn b() {} + fn c() {} +} + +impl TraitUnorderedItemKinds for StructUnordered { + const A: bool = false; + const B: bool = false; + const C: bool = false; + + type SomeType = (); + + fn a() {} + fn b() {} + fn c() {} +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnordered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnorderedItemKinds { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs new file mode 100644 index 000000000000..0fccbd4790bc --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs @@ -0,0 +1,174 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: var_1 +//@[var_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/var_1 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + fn a() {} + fn b() {} + fn c() {} + + type SomeType = (); + + const A: bool = false; + const B: bool = false; + const C: bool = false; +} + +impl TraitUnorderedItemKinds for StructUnordered { + fn a() {} + + type SomeType = (); + + const A: bool = false; +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnordered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnorderedItemKinds { + fn a(); + + type SomeType; + + const A: bool; +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr new file mode 100644 index 000000000000..062bf25ea624 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr @@ -0,0 +1,226 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:21:14 + | +LL | use std::rc::Weak; + | ^^^^ + | +note: should be placed before `SNAKE_CASE` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:19:7 + | +LL | const SNAKE_CASE: &str = "zzzzzzzz"; + | ^^^^^^^^^^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:64:1 + | +LL | / impl CloneSelf for StructOrdered { +LL | | fn clone_self(&self) -> Self { +LL | | Self { +LL | | a: true, +... | +LL | | } +LL | | } + | |_^ + | +note: should be placed before the following item + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:54:1 + | +LL | / impl Default for StructOrdered { +LL | | fn default() -> Self { +LL | | Self { +LL | | a: true, +... | +LL | | } +LL | | } + | |_^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:136:7 + | +LL | const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `TraitUnorderedItemKinds` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:124:7 + | +LL | trait TraitUnorderedItemKinds { + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:151:1 + | +LL | impl BasicEmptyTrait for StructOrdered {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before the following item + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:138:1 + | +LL | / impl TraitUnordered for StructUnordered { +LL | | const A: bool = false; +LL | | const C: bool = false; +LL | | const B: bool = false; +... | +LL | | fn b() {} +LL | | } + | |_^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:170:5 + | +LL | mod this_is_in_the_wrong_position { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `main` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:165:4 + | +LL | fn main() { + | ^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:178:7 + | +LL | const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `ZisShouldBeBeforeZeMainFn` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:176:8 + | +LL | struct ZisShouldBeBeforeZeMainFn; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:12:11 + | +LL | const AFTER: i8 = 0; + | ^^^^^ + | +note: should be placed before `BEFORE` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:10:11 + | +LL | const BEFORE: i8 = 0; + | ^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:38:5 + | +LL | B, + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:37:5 + | +LL | C, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:88:5 + | +LL | b: bool, + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:87:5 + | +LL | c: bool, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:96:5 + | +LL | b: bool, + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:95:5 + | +LL | c: bool, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:115:11 + | +LL | const B: bool; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:114:11 + | +LL | const C: bool; + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:121:8 + | +LL | fn b(); + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:120:8 + | +LL | fn c(); + | ^ + +error: incorrect ordering of trait items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:127:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:125:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:141:11 + | +LL | const B: bool = false; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:140:11 + | +LL | const C: bool = false; + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:147:8 + | +LL | fn b() {} + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:146:8 + | +LL | fn c() {} + | ^ + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:156:5 + | +LL | const A: bool = false; + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:154:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:172:11 + | +LL | const A: i8 = 1; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:171:11 + | +LL | const C: i8 = 0; + | ^ + +error: aborting due to 17 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs new file mode 100644 index 000000000000..2765bf935b7c --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs @@ -0,0 +1,185 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const BEFORE: i8 = 0; + + const AFTER: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::Rc; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +use std::rc::Weak; + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + C, + B, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + C, + B, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Default, Clone)] +struct StructUnordered { + a: bool, + c: bool, + b: bool, + d: bool, +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + c: bool, + b: bool, + d: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnordered { + const A: bool; + const C: bool; + const B: bool; + + type SomeType; + + fn a(); + fn c(); + fn b(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + const B: bool; + const C: bool; + + fn a(); + fn b(); + fn c(); +} + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +impl TraitUnordered for StructUnordered { + const A: bool = false; + const C: bool = false; + const B: bool = false; + + type SomeType = (); + + fn a() {} + fn c() {} + fn b() {} +} + +// Trait impls should be located just after the type they implement it for. +impl BasicEmptyTrait for StructOrdered {} + +impl TraitUnorderedItemKinds for StructUnordered { + type SomeType = (); + + const A: bool = false; + const B: bool = false; + const C: bool = false; + + fn a() {} + fn b() {} + fn c() {} +} + +fn main() { + // test code goes here +} + +/// Note that the linting pass is stopped before recursing into this module. +mod this_is_in_the_wrong_position { + const C: i8 = 0; + const A: i8 = 1; +} + +#[derive(Default, std::clone::Clone)] +struct ZisShouldBeBeforeZeMainFn; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs new file mode 100644 index 000000000000..44902336573c --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs @@ -0,0 +1,174 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: var_1 +//@[var_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/var_1 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + fn a() {} + fn c() {} + fn b() {} + + type SomeType = (); + + const A: bool = false; + const C: bool = false; + const B: bool = false; +} + +impl TraitUnorderedItemKinds for StructUnordered { + const A: bool = false; + + type SomeType = (); + + fn a() {} +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnordered { + fn a(); + fn c(); + fn b(); + + type SomeType; + + const A: bool; + const C: bool; + const B: bool; +} + +trait TraitUnorderedItemKinds { + const A: bool; + + type SomeType; + + fn a(); +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr new file mode 100644 index 000000000000..f31f7f68c17e --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr @@ -0,0 +1,100 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:105:8 + | +LL | fn b() {} + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:104:8 + | +LL | fn c() {} + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:111:11 + | +LL | const B: bool = false; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:110:11 + | +LL | const C: bool = false; + | ^ + +error: incorrect ordering of impl items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:117:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `A` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:115:5 + | +LL | const A: bool = false; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of impl items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:119:5 + | +LL | fn a() {} + | ^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:117:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:145:8 + | +LL | fn b(); + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:144:8 + | +LL | fn c(); + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:151:11 + | +LL | const B: bool; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:150:11 + | +LL | const C: bool; + | ^ + +error: incorrect ordering of trait items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:157:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `A` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:155:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + +error: incorrect ordering of trait items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:159:5 + | +LL | fn a(); + | ^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:157:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr new file mode 100644 index 000000000000..57069fd8feed --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr @@ -0,0 +1,16 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs:22:5 + | +LL | A, + | ^ + | +note: should be placed before `B` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs:21:5 + | +LL | B, + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs new file mode 100644 index 000000000000..e8002c4a1633 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs @@ -0,0 +1,43 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_enum +//@[only_enum] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_enum + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +enum EnumOrdered { + A, + B, +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr new file mode 100644 index 000000000000..40348ecbdae1 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr @@ -0,0 +1,40 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:43:8 + | +LL | fn a() {} + | ^ + | +note: should be placed before `b` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:42:8 + | +LL | fn b() {} + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:45:5 + | +LL | type SomeType = i8; + | ^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `a` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:43:5 + | +LL | fn a() {} + | ^^^^^^^^^ + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:47:5 + | +LL | const A: bool = true; + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:45:5 + | +LL | type SomeType = i8; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs new file mode 100644 index 000000000000..bd969c865b5a --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs @@ -0,0 +1,67 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_impl +//@[only_impl] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_impl + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +struct BasicStruct {} + +trait BasicTrait { + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +impl BasicTrait for StructUnordered { + fn b() {} + fn a() {} + + type SomeType = i8; + + const A: bool = true; +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); + +impl BasicTrait for BasicStruct { + const A: bool = true; + + type SomeType = i8; + + fn a() {} + fn b() {} +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr new file mode 100644 index 000000000000..9b86ebd48e3d --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr @@ -0,0 +1,40 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:32:11 + | +LL | const A: bool; + | ^ + | +note: should be placed before `B` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:31:11 + | +LL | const B: bool; + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:37:8 + | +LL | fn a(); + | ^ + | +note: should be placed before `b` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:36:8 + | +LL | fn b(); + | ^ + +error: incorrect ordering of trait items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:43:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:41:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs new file mode 100644 index 000000000000..979a52ecb100 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs @@ -0,0 +1,48 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_trait +//@[only_trait] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_trait + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + + type SomeType; + + fn a(); + fn b(); +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); diff --git a/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml new file mode 100644 index 000000000000..bede035e3885 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["fn", "type", "const"] diff --git a/tests/ui-toml/disallowed_macros/disallowed_macros.stderr b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr index ddeb2f8cc704..2e3386628b4d 100644 --- a/tests/ui-toml/disallowed_macros/disallowed_macros.stderr +++ b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr @@ -1,11 +1,39 @@ +error: use of a disallowed macro `std::vec` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:16:5 + | +LL | vec![1, 2, 3]; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-macros` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_macros)]` + +error: use of a disallowed macro `serde::Serialize` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:18:14 + | +LL | #[derive(Serialize)] + | ^^^^^^^^^ + | + = note: no serializing + +error: use of a disallowed macro `macros::attr` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:31:1 + | +LL | / macros::attr! { +LL | | struct S; +LL | | } + | |_^ + +error: use of a disallowed macro `proc_macros::Derive` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:47:10 + | +LL | #[derive(Derive)] + | ^^^^^^ + error: use of a disallowed macro `std::println` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:13:5 | LL | println!("one"); | ^^^^^^^^^^^^^^^ - | - = note: `-D clippy::disallowed-macros` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::disallowed_macros)]` error: use of a disallowed macro `std::println` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:14:5 @@ -19,20 +47,6 @@ error: use of a disallowed macro `std::cfg` LL | cfg!(unix); | ^^^^^^^^^^ -error: use of a disallowed macro `std::vec` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:16:5 - | -LL | vec![1, 2, 3]; - | ^^^^^^^^^^^^^ - -error: use of a disallowed macro `serde::Serialize` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:18:14 - | -LL | #[derive(Serialize)] - | ^^^^^^^^^ - | - = note: no serializing - error: use of a disallowed macro `macros::expr` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:21:13 | @@ -69,14 +83,6 @@ error: use of a disallowed macro `macros::binop` LL | let _ = macros::binop!(1); | ^^^^^^^^^^^^^^^^^ -error: use of a disallowed macro `macros::attr` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:31:1 - | -LL | / macros::attr! { -LL | | struct S; -LL | | } - | |_^ - error: use of a disallowed macro `macros::item` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:36:5 | @@ -95,11 +101,5 @@ error: use of a disallowed macro `macros::item` LL | macros::item!(); | ^^^^^^^^^^^^^^^ -error: use of a disallowed macro `proc_macros::Derive` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:47:10 - | -LL | #[derive(Derive)] - | ^^^^^^ - error: aborting due to 16 previous errors diff --git a/tests/ui-toml/large_include_file/large_include_file.rs b/tests/ui-toml/large_include_file/large_include_file.rs index f3dbb6ad1cf5..dc9349f75a08 100644 --- a/tests/ui-toml/large_include_file/large_include_file.rs +++ b/tests/ui-toml/large_include_file/large_include_file.rs @@ -1,8 +1,8 @@ #![warn(clippy::large_include_file)] // Good -const GOOD_INCLUDE_BYTES: &[u8; 581] = include_bytes!("large_include_file.rs"); -const GOOD_INCLUDE_STR: &str = include_str!("large_include_file.rs"); +const GOOD_INCLUDE_BYTES: &[u8; 68] = include_bytes!("../../ui/author.rs"); +const GOOD_INCLUDE_STR: &str = include_str!("../../ui/author.rs"); #[allow(clippy::large_include_file)] const ALLOWED_TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); @@ -11,6 +11,9 @@ const ALLOWED_TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); // Bad const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); +//~^ large_include_file const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); +//~^ large_include_file +#[doc = include_str!("too_big.txt")] //~ large_include_file fn main() {} diff --git a/tests/ui-toml/large_include_file/large_include_file.stderr b/tests/ui-toml/large_include_file/large_include_file.stderr index 34224065f078..9e1494a47bba 100644 --- a/tests/ui-toml/large_include_file/large_include_file.stderr +++ b/tests/ui-toml/large_include_file/large_include_file.stderr @@ -9,12 +9,20 @@ LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); = help: to override `-D warnings` add `#[allow(clippy::large_include_file)]` error: attempted to include a large file - --> tests/ui-toml/large_include_file/large_include_file.rs:14:35 + --> tests/ui-toml/large_include_file/large_include_file.rs:15:35 | LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the configuration allows a maximum size of 600 bytes -error: aborting due to 2 previous errors +error: attempted to include a large file + --> tests/ui-toml/large_include_file/large_include_file.rs:18:1 + | +LL | #[doc = include_str!("too_big.txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the configuration allows a maximum size of 600 bytes + +error: aborting due to 3 previous errors diff --git a/tests/ui-toml/private-doc-errors/doc_lints.rs b/tests/ui-toml/private-doc-errors/doc_lints.rs index 79c8751468de..c5d00c91b052 100644 --- a/tests/ui-toml/private-doc-errors/doc_lints.rs +++ b/tests/ui-toml/private-doc-errors/doc_lints.rs @@ -51,4 +51,10 @@ pub mod __macro { } } +#[warn(clippy::missing_errors_doc)] +#[test] +fn test() -> Result<(), ()> { + Ok(()) +} + fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 5cf9c0fb2710..6fa583fc0417 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -54,12 +54,14 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -68,6 +70,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size @@ -138,12 +141,14 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -152,6 +157,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size @@ -222,12 +228,14 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -236,6 +244,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size diff --git a/tests/ui/allow_attributes.fixed b/tests/ui/allow_attributes.fixed index 058dbb77a320..8f6c962e2f5b 100644 --- a/tests/ui/allow_attributes.fixed +++ b/tests/ui/allow_attributes.fixed @@ -1,4 +1,5 @@ //@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs #![allow(unused)] #![warn(clippy::allow_attributes)] #![no_main] @@ -65,3 +66,9 @@ fn deny_allow_attributes() -> Option { allow?; Some(42) } + +// Edge case where the generated tokens spans match on #[repr(transparent)] which tricks the proc +// macro check +#[repr(transparent)] +#[derive(proc_macro_derive::AllowLintSameSpan)] // This macro generates tokens with the same span as the whole struct and repr +struct IgnoreDerived; diff --git a/tests/ui/allow_attributes.rs b/tests/ui/allow_attributes.rs index 6d94ce50e4c3..cb6c4dcf7158 100644 --- a/tests/ui/allow_attributes.rs +++ b/tests/ui/allow_attributes.rs @@ -1,4 +1,5 @@ //@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs #![allow(unused)] #![warn(clippy::allow_attributes)] #![no_main] @@ -65,3 +66,9 @@ fn deny_allow_attributes() -> Option { allow?; Some(42) } + +// Edge case where the generated tokens spans match on #[repr(transparent)] which tricks the proc +// macro check +#[repr(transparent)] +#[derive(proc_macro_derive::AllowLintSameSpan)] // This macro generates tokens with the same span as the whole struct and repr +struct IgnoreDerived; diff --git a/tests/ui/allow_attributes.stderr b/tests/ui/allow_attributes.stderr index 023b4d7e4043..5a4ff287acd2 100644 --- a/tests/ui/allow_attributes.stderr +++ b/tests/ui/allow_attributes.stderr @@ -1,5 +1,5 @@ error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:12:3 + --> tests/ui/allow_attributes.rs:13:3 | LL | #[allow(dead_code)] | ^^^^^ help: replace it with: `expect` @@ -8,13 +8,13 @@ LL | #[allow(dead_code)] = help: to override `-D warnings` add `#[allow(clippy::allow_attributes)]` error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:21:30 + --> tests/ui/allow_attributes.rs:22:30 | LL | #[cfg_attr(panic = "unwind", allow(dead_code))] | ^^^^^ help: replace it with: `expect` error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:52:7 + --> tests/ui/allow_attributes.rs:53:7 | LL | #[allow(unused)] | ^^^^^ help: replace it with: `expect` diff --git a/tests/ui-internal/author.rs b/tests/ui/author.rs similarity index 72% rename from tests/ui-internal/author.rs rename to tests/ui/author.rs index eb1f3e3f8704..0a1be3568967 100644 --- a/tests/ui-internal/author.rs +++ b/tests/ui/author.rs @@ -1,5 +1,3 @@ -#![warn(clippy::author)] - fn main() { #[clippy::author] let x: char = 0x45 as char; diff --git a/tests/ui-internal/author.stdout b/tests/ui/author.stdout similarity index 100% rename from tests/ui-internal/author.stdout rename to tests/ui/author.stdout diff --git a/tests/ui-internal/author/blocks.rs b/tests/ui/author/blocks.rs similarity index 100% rename from tests/ui-internal/author/blocks.rs rename to tests/ui/author/blocks.rs diff --git a/tests/ui-internal/author/blocks.stdout b/tests/ui/author/blocks.stdout similarity index 100% rename from tests/ui-internal/author/blocks.stdout rename to tests/ui/author/blocks.stdout diff --git a/tests/ui-internal/author/call.rs b/tests/ui/author/call.rs similarity index 100% rename from tests/ui-internal/author/call.rs rename to tests/ui/author/call.rs diff --git a/tests/ui-internal/author/call.stdout b/tests/ui/author/call.stdout similarity index 100% rename from tests/ui-internal/author/call.stdout rename to tests/ui/author/call.stdout diff --git a/tests/ui-internal/author/if.rs b/tests/ui/author/if.rs similarity index 100% rename from tests/ui-internal/author/if.rs rename to tests/ui/author/if.rs diff --git a/tests/ui-internal/author/if.stdout b/tests/ui/author/if.stdout similarity index 100% rename from tests/ui-internal/author/if.stdout rename to tests/ui/author/if.stdout diff --git a/tests/ui-internal/author/issue_3849.rs b/tests/ui/author/issue_3849.rs similarity index 100% rename from tests/ui-internal/author/issue_3849.rs rename to tests/ui/author/issue_3849.rs diff --git a/tests/ui-internal/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout similarity index 100% rename from tests/ui-internal/author/issue_3849.stdout rename to tests/ui/author/issue_3849.stdout diff --git a/tests/ui-internal/author/loop.rs b/tests/ui/author/loop.rs similarity index 100% rename from tests/ui-internal/author/loop.rs rename to tests/ui/author/loop.rs diff --git a/tests/ui-internal/author/loop.stdout b/tests/ui/author/loop.stdout similarity index 100% rename from tests/ui-internal/author/loop.stdout rename to tests/ui/author/loop.stdout diff --git a/tests/ui-internal/author/macro_in_closure.rs b/tests/ui/author/macro_in_closure.rs similarity index 100% rename from tests/ui-internal/author/macro_in_closure.rs rename to tests/ui/author/macro_in_closure.rs diff --git a/tests/ui-internal/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout similarity index 100% rename from tests/ui-internal/author/macro_in_closure.stdout rename to tests/ui/author/macro_in_closure.stdout diff --git a/tests/ui-internal/author/macro_in_loop.rs b/tests/ui/author/macro_in_loop.rs similarity index 100% rename from tests/ui-internal/author/macro_in_loop.rs rename to tests/ui/author/macro_in_loop.rs diff --git a/tests/ui-internal/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout similarity index 100% rename from tests/ui-internal/author/macro_in_loop.stdout rename to tests/ui/author/macro_in_loop.stdout diff --git a/tests/ui-internal/author/matches.rs b/tests/ui/author/matches.rs similarity index 100% rename from tests/ui-internal/author/matches.rs rename to tests/ui/author/matches.rs diff --git a/tests/ui-internal/author/matches.stdout b/tests/ui/author/matches.stdout similarity index 100% rename from tests/ui-internal/author/matches.stdout rename to tests/ui/author/matches.stdout diff --git a/tests/ui-internal/author/repeat.rs b/tests/ui/author/repeat.rs similarity index 100% rename from tests/ui-internal/author/repeat.rs rename to tests/ui/author/repeat.rs diff --git a/tests/ui-internal/author/repeat.stdout b/tests/ui/author/repeat.stdout similarity index 100% rename from tests/ui-internal/author/repeat.stdout rename to tests/ui/author/repeat.stdout diff --git a/tests/ui-internal/author/struct.rs b/tests/ui/author/struct.rs similarity index 100% rename from tests/ui-internal/author/struct.rs rename to tests/ui/author/struct.rs diff --git a/tests/ui-internal/author/struct.stdout b/tests/ui/author/struct.stdout similarity index 100% rename from tests/ui-internal/author/struct.stdout rename to tests/ui/author/struct.stdout diff --git a/tests/ui/auxiliary/proc_macro_derive.rs b/tests/ui/auxiliary/proc_macro_derive.rs index bd90042c1da8..fbf84337382e 100644 --- a/tests/ui/auxiliary/proc_macro_derive.rs +++ b/tests/ui/auxiliary/proc_macro_derive.rs @@ -1,4 +1,4 @@ -#![feature(repr128, proc_macro_quote)] +#![feature(repr128, proc_macro_quote, proc_macro_span)] #![allow(incomplete_features)] #![allow(clippy::field_reassign_with_default)] #![allow(clippy::eq_op)] @@ -182,3 +182,51 @@ pub fn non_canonical_clone_derive(_: TokenStream) -> TokenStream { impl Copy for NonCanonicalClone {} } } + +// Derive macro that generates the following but where all generated spans are set to the entire +// input span. +// +// ``` +// #[allow(clippy::missing_const_for_fn)] +// fn check() {} +// ``` +#[proc_macro_derive(AllowLintSameSpan)] +pub fn allow_lint_same_span_derive(input: TokenStream) -> TokenStream { + let mut iter = input.into_iter(); + let first = iter.next().unwrap(); + let last = iter.last().unwrap(); + let span = first.span().join(last.span()).unwrap(); + let span_help = |mut t: TokenTree| -> TokenTree { + t.set_span(span); + t + }; + // Generate the TokenStream but setting all the spans to the entire input span + >::from_iter([ + span_help(Punct::new('#', Spacing::Alone).into()), + span_help( + Group::new( + Delimiter::Bracket, + >::from_iter([ + Ident::new("allow", span).into(), + span_help( + Group::new( + Delimiter::Parenthesis, + >::from_iter([ + Ident::new("clippy", span).into(), + span_help(Punct::new(':', Spacing::Joint).into()), + span_help(Punct::new(':', Spacing::Alone).into()), + Ident::new("missing_const_for_fn", span).into(), + ]), + ) + .into(), + ), + ]), + ) + .into(), + ), + Ident::new("fn", span).into(), + Ident::new("check", span).into(), + span_help(Group::new(Delimiter::Parenthesis, TokenStream::new()).into()), + span_help(Group::new(Delimiter::Brace, TokenStream::new()).into()), + ]) +} diff --git a/tests/ui/borrow_deref_ref.fixed b/tests/ui/borrow_deref_ref.fixed index ea5e983de3bd..22e984c46d24 100644 --- a/tests/ui/borrow_deref_ref.fixed +++ b/tests/ui/borrow_deref_ref.fixed @@ -71,3 +71,9 @@ mod false_negative { assert_ne!(addr_x, addr_y); } } + +fn issue_13584() { + let s = "Hello, world!\n"; + let p = &raw const *s; + let _ = p as *const i8; +} diff --git a/tests/ui/borrow_deref_ref.rs b/tests/ui/borrow_deref_ref.rs index 8c8905b150e6..61d89193f42f 100644 --- a/tests/ui/borrow_deref_ref.rs +++ b/tests/ui/borrow_deref_ref.rs @@ -71,3 +71,9 @@ mod false_negative { assert_ne!(addr_x, addr_y); } } + +fn issue_13584() { + let s = "Hello, world!\n"; + let p = &raw const *s; + let _ = p as *const i8; +} diff --git a/tests/ui/const_is_empty.rs b/tests/ui/const_is_empty.rs index 04e0de91ecfb..b5e27b61548a 100644 --- a/tests/ui/const_is_empty.rs +++ b/tests/ui/const_is_empty.rs @@ -171,3 +171,17 @@ fn constant_from_external_crate() { let _ = std::env::consts::EXE_EXTENSION.is_empty(); // Do not lint, `exe_ext` comes from the `std` crate } + +fn issue_13106() { + const { + assert!(!NON_EMPTY_STR.is_empty()); + } + + const { + assert!(EMPTY_STR.is_empty()); + } + + const { + EMPTY_STR.is_empty(); + } +} diff --git a/tests/ui/const_is_empty.stderr b/tests/ui/const_is_empty.stderr index 7f80b520b1a4..0afba940d8b4 100644 --- a/tests/ui/const_is_empty.stderr +++ b/tests/ui/const_is_empty.stderr @@ -157,5 +157,11 @@ error: this expression always evaluates to true LL | let _ = val.is_empty(); | ^^^^^^^^^^^^^^ -error: aborting due to 26 previous errors +error: this expression always evaluates to true + --> tests/ui/const_is_empty.rs:185:9 + | +LL | EMPTY_STR.is_empty(); + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 27 previous errors diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 355f2bc77361..5cf5c608a85c 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -54,8 +54,9 @@ fn test_units() { /// This tests allowed identifiers. /// KiB MiB GiB TiB PiB EiB +/// MHz GHz THz /// AccessKit -/// CoreFoundation CoreGraphics CoreText +/// CoAP CoreFoundation CoreGraphics CoreText /// Direct2D Direct3D DirectWrite DirectX /// ECMAScript /// GPLv2 GPLv3 diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 9ced26776221..420211c65390 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -54,8 +54,9 @@ fn test_units() { /// This tests allowed identifiers. /// KiB MiB GiB TiB PiB EiB +/// MHz GHz THz /// AccessKit -/// CoreFoundation CoreGraphics CoreText +/// CoAP CoreFoundation CoreGraphics CoreText /// Direct2D Direct3D DirectWrite DirectX /// ECMAScript /// GPLv2 GPLv3 diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index 67c0464149c6..27a04e4b5583 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -133,7 +133,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:78:5 + --> tests/ui/doc/doc-fixable.rs:79:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -144,7 +144,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:95:5 + --> tests/ui/doc/doc-fixable.rs:96:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -155,7 +155,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:103:8 + --> tests/ui/doc/doc-fixable.rs:104:8 | LL | /// ## CamelCaseThing | ^^^^^^^^^^^^^^ @@ -166,7 +166,7 @@ LL | /// ## `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:106:7 + --> tests/ui/doc/doc-fixable.rs:107:7 | LL | /// # CamelCaseThing | ^^^^^^^^^^^^^^ @@ -177,7 +177,7 @@ LL | /// # `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:108:22 + --> tests/ui/doc/doc-fixable.rs:109:22 | LL | /// Not a title #897 CamelCaseThing | ^^^^^^^^^^^^^^ @@ -188,7 +188,7 @@ LL | /// Not a title #897 `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:109:5 + --> tests/ui/doc/doc-fixable.rs:110:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -199,7 +199,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:116:5 + --> tests/ui/doc/doc-fixable.rs:117:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -210,7 +210,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:129:5 + --> tests/ui/doc/doc-fixable.rs:130:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -221,7 +221,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:140:43 + --> tests/ui/doc/doc-fixable.rs:141:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -232,7 +232,7 @@ LL | /** E.g., serialization of an empty list: `FooBar` | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:145:5 + --> tests/ui/doc/doc-fixable.rs:146:5 | LL | And BarQuz too. | ^^^^^^ @@ -243,7 +243,7 @@ LL | And `BarQuz` too. | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:146:1 + --> tests/ui/doc/doc-fixable.rs:147:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -254,7 +254,7 @@ LL | `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:151:43 + --> tests/ui/doc/doc-fixable.rs:152:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -265,7 +265,7 @@ LL | /** E.g., serialization of an empty list: `FooBar` | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:156:5 + --> tests/ui/doc/doc-fixable.rs:157:5 | LL | And BarQuz too. | ^^^^^^ @@ -276,7 +276,7 @@ LL | And `BarQuz` too. | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:157:1 + --> tests/ui/doc/doc-fixable.rs:158:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -287,7 +287,7 @@ LL | `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:168:5 + --> tests/ui/doc/doc-fixable.rs:169:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -298,7 +298,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:187:22 + --> tests/ui/doc/doc-fixable.rs:188:22 | LL | /// An iterator over mycrate::Collection's values. | ^^^^^^^^^^^^^^^^^^^ @@ -309,7 +309,7 @@ LL | /// An iterator over `mycrate::Collection`'s values. | ~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:211:34 + --> tests/ui/doc/doc-fixable.rs:212:34 | LL | /// Foo \[bar\] \[baz\] \[qux\]. DocMarkdownLint | ^^^^^^^^^^^^^^^ @@ -320,7 +320,7 @@ LL | /// Foo \[bar\] \[baz\] \[qux\]. `DocMarkdownLint` | ~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:234:22 + --> tests/ui/doc/doc-fixable.rs:235:22 | LL | /// There is no try (do() or do_not()). | ^^^^ @@ -331,7 +331,7 @@ LL | /// There is no try (`do()` or do_not()). | ~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:234:30 + --> tests/ui/doc/doc-fixable.rs:235:30 | LL | /// There is no try (do() or do_not()). | ^^^^^^^^ @@ -342,7 +342,7 @@ LL | /// There is no try (do() or `do_not()`). | ~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:237:5 + --> tests/ui/doc/doc-fixable.rs:238:5 | LL | /// ABes | ^^^^ @@ -353,7 +353,7 @@ LL | /// `ABes` | ~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:243:9 + --> tests/ui/doc/doc-fixable.rs:244:9 | LL | /// foo() | ^^^^^ @@ -364,7 +364,7 @@ LL | /// `foo()` | ~~~~~~~ error: you should put bare URLs between `<`/`>` or make a proper Markdown link - --> tests/ui/doc/doc-fixable.rs:247:5 + --> tests/ui/doc/doc-fixable.rs:248:5 | LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index b6cb7ff49b0a..0613eddce266 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -3,6 +3,7 @@ #![allow(clippy::never_loop)] #![warn(clippy::infinite_loop)] +#![feature(async_closure)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -428,4 +429,23 @@ fn continue_outer() { } } +// don't suggest adding `-> !` to async fn/closure that already returning `-> !` +mod issue_12338 { + use super::do_something; + + async fn foo() -> ! { + loop { + do_something(); + } + } + + fn bar() { + let _ = async || -> ! { + loop { + do_something(); + } + }; + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index 7635a7442f4a..3a3ed7d0fe8f 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -1,5 +1,5 @@ error: infinite loop detected - --> tests/ui/infinite_loops.rs:13:5 + --> tests/ui/infinite_loops.rs:14:5 | LL | / loop { LL | | @@ -15,7 +15,7 @@ LL | fn no_break() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:20:5 + --> tests/ui/infinite_loops.rs:21:5 | LL | / loop { LL | | @@ -32,7 +32,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:22:9 + --> tests/ui/infinite_loops.rs:23:9 | LL | / loop { LL | | @@ -49,7 +49,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:24:13 + --> tests/ui/infinite_loops.rs:25:13 | LL | / loop { LL | | @@ -63,7 +63,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:38:5 + --> tests/ui/infinite_loops.rs:39:5 | LL | / loop { LL | | @@ -74,7 +74,7 @@ LL | | } = help: if this is not intended, try adding a `break` or `return` condition in the loop error: infinite loop detected - --> tests/ui/infinite_loops.rs:51:5 + --> tests/ui/infinite_loops.rs:52:5 | LL | / loop { LL | | fn inner_fn() -> ! { @@ -90,7 +90,7 @@ LL | fn no_break_never_ret_noise() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:94:5 + --> tests/ui/infinite_loops.rs:95:5 | LL | / loop { LL | | @@ -107,7 +107,7 @@ LL | fn break_inner_but_not_outer_1(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:105:5 + --> tests/ui/infinite_loops.rs:106:5 | LL | / loop { LL | | @@ -124,7 +124,7 @@ LL | fn break_inner_but_not_outer_2(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:119:9 + --> tests/ui/infinite_loops.rs:120:9 | LL | / loop { LL | | @@ -138,7 +138,7 @@ LL | fn break_outer_but_not_inner() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:142:9 + --> tests/ui/infinite_loops.rs:143:9 | LL | / loop { LL | | @@ -155,7 +155,7 @@ LL | fn break_wrong_loop(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:182:5 + --> tests/ui/infinite_loops.rs:183:5 | LL | / loop { LL | | @@ -172,7 +172,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:223:5 + --> tests/ui/infinite_loops.rs:224:5 | LL | / loop { LL | | @@ -186,7 +186,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:228:5 + --> tests/ui/infinite_loops.rs:229:5 | LL | / loop { LL | | @@ -203,7 +203,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:333:9 + --> tests/ui/infinite_loops.rs:334:9 | LL | / loop { LL | | @@ -217,7 +217,7 @@ LL | fn problematic_trait_method() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:343:9 + --> tests/ui/infinite_loops.rs:344:9 | LL | / loop { LL | | @@ -231,7 +231,7 @@ LL | fn could_be_problematic() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:352:9 + --> tests/ui/infinite_loops.rs:353:9 | LL | / loop { LL | | @@ -245,7 +245,7 @@ LL | let _loop_forever = || -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:366:8 + --> tests/ui/infinite_loops.rs:367:8 | LL | Ok(loop { | ________^ @@ -256,7 +256,7 @@ LL | | }) = help: if this is not intended, try adding a `break` or `return` condition in the loop error: infinite loop detected - --> tests/ui/infinite_loops.rs:408:5 + --> tests/ui/infinite_loops.rs:409:5 | LL | / 'infinite: loop { LL | | @@ -272,7 +272,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:415:5 + --> tests/ui/infinite_loops.rs:416:5 | LL | / loop { LL | | @@ -289,7 +289,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:417:9 + --> tests/ui/infinite_loops.rs:418:9 | LL | / 'inner: loop { LL | | loop { @@ -304,7 +304,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:425:5 + --> tests/ui/infinite_loops.rs:426:5 | LL | / loop { LL | | diff --git a/tests/ui/iter_without_into_iter.stderr b/tests/ui/iter_without_into_iter.stderr index 7c42fa1dd89c..d748c85003bb 100644 --- a/tests/ui/iter_without_into_iter.stderr +++ b/tests/ui/iter_without_into_iter.stderr @@ -13,8 +13,8 @@ help: consider implementing `IntoIterator` for `&S1` | LL + LL + impl IntoIterator for &S1 { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -34,8 +34,8 @@ help: consider implementing `IntoIterator` for `&mut S1` | LL + LL + impl IntoIterator for &mut S1 { -LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + type Item = &mut u8; +LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -55,8 +55,8 @@ help: consider implementing `IntoIterator` for `&S3<'a>` | LL + LL + impl IntoIterator for &S3<'a> { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -76,8 +76,8 @@ help: consider implementing `IntoIterator` for `&mut S3<'a>` | LL + LL + impl IntoIterator for &mut S3<'a> { -LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + type Item = &mut u8; +LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -96,8 +96,8 @@ help: consider implementing `IntoIterator` for `&S8` | LL + LL + impl IntoIterator for &S8 { -LL + type IntoIter = std::slice::Iter<'static, T>; LL + type Item = &T; +LL + type IntoIter = std::slice::Iter<'static, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -117,8 +117,8 @@ help: consider implementing `IntoIterator` for `&S9` | LL + LL + impl IntoIterator for &S9 { -LL + type IntoIter = std::slice::Iter<'_, T>; LL + type Item = &T; +LL + type IntoIter = std::slice::Iter<'_, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -138,8 +138,8 @@ help: consider implementing `IntoIterator` for `&mut S9` | LL + LL + impl IntoIterator for &mut S9 { -LL + type IntoIter = std::slice::IterMut<'_, T>; LL + type Item = &mut T; +LL + type IntoIter = std::slice::IterMut<'_, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -162,8 +162,8 @@ help: consider implementing `IntoIterator` for `&Issue12037` | LL ~ LL + impl IntoIterator for &Issue12037 { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } diff --git a/tests/ui/large_const_arrays.fixed b/tests/ui/large_const_arrays.fixed index 543ce460e7ba..e5b28cb6a9dd 100644 --- a/tests/ui/large_const_arrays.fixed +++ b/tests/ui/large_const_arrays.fixed @@ -9,11 +9,13 @@ pub struct S { // Should lint pub(crate) static FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; pub static FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +static FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; static FOO: [u32; 1_000_000] = [0u32; 1_000_000]; // Good pub(crate) const G_FOO_PUB_CRATE: [u32; 250] = [0u32; 250]; pub const G_FOO_PUB: [u32; 250] = [0u32; 250]; +const G_FOO_COMPUTED: [u32; 25 * 10] = [0u32; 25 * 10]; const G_FOO: [u32; 250] = [0u32; 250]; fn main() { diff --git a/tests/ui/large_const_arrays.rs b/tests/ui/large_const_arrays.rs index e23a8081171d..b9593225da2f 100644 --- a/tests/ui/large_const_arrays.rs +++ b/tests/ui/large_const_arrays.rs @@ -9,11 +9,13 @@ pub struct S { // Should lint pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +const FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; // Good pub(crate) const G_FOO_PUB_CRATE: [u32; 250] = [0u32; 250]; pub const G_FOO_PUB: [u32; 250] = [0u32; 250]; +const G_FOO_COMPUTED: [u32; 25 * 10] = [0u32; 25 * 10]; const G_FOO: [u32; 250] = [0u32; 250]; fn main() { diff --git a/tests/ui/large_const_arrays.stderr b/tests/ui/large_const_arrays.stderr index 88f921b81f72..271122053908 100644 --- a/tests/ui/large_const_arrays.stderr +++ b/tests/ui/large_const_arrays.stderr @@ -20,13 +20,21 @@ LL | pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; error: large array defined as const --> tests/ui/large_const_arrays.rs:12:1 | +LL | const FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> tests/ui/large_const_arrays.rs:13:1 + | LL | const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:21:5 + --> tests/ui/large_const_arrays.rs:23:5 | LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +42,7 @@ LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:22:5 + --> tests/ui/large_const_arrays.rs:24:5 | LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,7 +50,7 @@ LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:23:5 + --> tests/ui/large_const_arrays.rs:25:5 | LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,7 +58,7 @@ LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:24:5 + --> tests/ui/large_const_arrays.rs:26:5 | LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -58,7 +66,7 @@ LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:25:5 + --> tests/ui/large_const_arrays.rs:27:5 | LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -66,12 +74,12 @@ LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:26:5 + --> tests/ui/large_const_arrays.rs:28:5 | LL | const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | help: make this a static item: `static` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_bits.fixed b/tests/ui/manual_bits.fixed index 4de01905e8ad..8e5cb7d38b9b 100644 --- a/tests/ui/manual_bits.fixed +++ b/tests/ui/manual_bits.fixed @@ -56,3 +56,14 @@ fn main() { let _ = (u128::BITS as usize).pow(5); let _ = &(u128::BITS as usize); } + +fn should_not_lint() { + macro_rules! bits_via_macro { + ($T: ty) => { + size_of::<$T>() * 8; + }; + } + + bits_via_macro!(u8); + bits_via_macro!(String); +} diff --git a/tests/ui/manual_bits.rs b/tests/ui/manual_bits.rs index d4f369fcf87d..5e492ed15d21 100644 --- a/tests/ui/manual_bits.rs +++ b/tests/ui/manual_bits.rs @@ -56,3 +56,14 @@ fn main() { let _ = (size_of::() * 8).pow(5); let _ = &(size_of::() * 8); } + +fn should_not_lint() { + macro_rules! bits_via_macro { + ($T: ty) => { + size_of::<$T>() * 8; + }; + } + + bits_via_macro!(u8); + bits_via_macro!(String); +} diff --git a/tests/ui/map_all_any_identity.fixed b/tests/ui/map_all_any_identity.fixed new file mode 100644 index 000000000000..c92462ab9c27 --- /dev/null +++ b/tests/ui/map_all_any_identity.fixed @@ -0,0 +1,21 @@ +#![warn(clippy::map_all_any_identity)] + +fn main() { + let _ = ["foo"].into_iter().any(|s| s == "foo"); + //~^ map_all_any_identity + let _ = ["foo"].into_iter().all(|s| s == "foo"); + //~^ map_all_any_identity + + // + // Do not lint + // + // Not identity + let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0); + // Macro + macro_rules! map { + ($x:expr) => { + $x.into_iter().map(|s| s == "foo") + }; + } + map!(["foo"]).any(|a| a); +} diff --git a/tests/ui/map_all_any_identity.rs b/tests/ui/map_all_any_identity.rs new file mode 100644 index 000000000000..0e4a564ca046 --- /dev/null +++ b/tests/ui/map_all_any_identity.rs @@ -0,0 +1,21 @@ +#![warn(clippy::map_all_any_identity)] + +fn main() { + let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a); + //~^ map_all_any_identity + let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity); + //~^ map_all_any_identity + + // + // Do not lint + // + // Not identity + let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0); + // Macro + macro_rules! map { + ($x:expr) => { + $x.into_iter().map(|s| s == "foo") + }; + } + map!(["foo"]).any(|a| a); +} diff --git a/tests/ui/map_all_any_identity.stderr b/tests/ui/map_all_any_identity.stderr new file mode 100644 index 000000000000..98fdcc2a9393 --- /dev/null +++ b/tests/ui/map_all_any_identity.stderr @@ -0,0 +1,26 @@ +error: usage of `.map(...).any(identity)` + --> tests/ui/map_all_any_identity.rs:4:33 + | +LL | let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-all-any-identity` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_all_any_identity)]` +help: use `.any(...)` instead + | +LL | let _ = ["foo"].into_iter().any(|s| s == "foo"); + | ~~~~~~~~~~~~~~~~~~~ + +error: usage of `.map(...).all(identity)` + --> tests/ui/map_all_any_identity.rs:6:33 + | +LL | let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `.all(...)` instead + | +LL | let _ = ["foo"].into_iter().all(|s| s == "foo"); + | ~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/map_with_unused_argument_over_ranges.fixed b/tests/ui/map_with_unused_argument_over_ranges.fixed new file mode 100644 index 000000000000..cf520e71a64f --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.fixed @@ -0,0 +1,73 @@ +#![allow( + unused, + clippy::redundant_closure, + clippy::reversed_empty_ranges, + clippy::identity_op +)] +#![warn(clippy::map_with_unused_argument_over_ranges)] + +fn do_something() -> usize { + todo!() +} + +fn do_something_interesting(x: usize, y: usize) -> usize { + todo!() +} + +macro_rules! gen { + () => { + (0..10).map(|_| do_something()); + }; +} + +fn main() { + // These should be raised + std::iter::repeat_with(|| do_something()).take(10); + std::iter::repeat_with(|| do_something()).take(10); + std::iter::repeat_with(|| do_something()).take(11); + std::iter::repeat_with(|| do_something()).take(7); + std::iter::repeat_with(|| do_something()).take(8); + std::iter::repeat_n(3, 10); + std::iter::repeat_with(|| { + let x = 3; + x + 2 + }).take(10); + std::iter::repeat_with(|| do_something()).take(10).collect::>(); + let upper = 4; + std::iter::repeat_with(|| do_something()).take(upper); + let upper_fn = || 4; + std::iter::repeat_with(|| do_something()).take(upper_fn()); + std::iter::repeat_with(|| do_something()).take(upper_fn() + 1); + std::iter::repeat_with(|| do_something()).take(upper_fn() - 2); + std::iter::repeat_with(|| do_something()).take(upper_fn() - 1); + (-3..9).map(|_| do_something()); + std::iter::repeat_with(|| do_something()).take(0); + std::iter::repeat_with(|| do_something()).take(1); + std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); + // These should not be raised + gen!(); + let lower = 2; + let lower_fn = || 2; + (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..=upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (0..10).map(|x| do_something_interesting(x, 4)); // Actual map over range + "Foobar".chars().map(|_| do_something()); // Not a map over range + // i128::MAX == 340282366920938463463374607431768211455 + (0..=340282366920938463463374607431768211455).map(|_: u128| do_something()); // Can't be replaced due to overflow +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.28"] +fn msrv_1_28() { + std::iter::repeat_with(|| do_something()).take(10); +} + +#[clippy::msrv = "1.81"] +fn msrv_1_82() { + std::iter::repeat(3).take(10); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.rs b/tests/ui/map_with_unused_argument_over_ranges.rs new file mode 100644 index 000000000000..298eee9ca3fa --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.rs @@ -0,0 +1,73 @@ +#![allow( + unused, + clippy::redundant_closure, + clippy::reversed_empty_ranges, + clippy::identity_op +)] +#![warn(clippy::map_with_unused_argument_over_ranges)] + +fn do_something() -> usize { + todo!() +} + +fn do_something_interesting(x: usize, y: usize) -> usize { + todo!() +} + +macro_rules! gen { + () => { + (0..10).map(|_| do_something()); + }; +} + +fn main() { + // These should be raised + (0..10).map(|_| do_something()); + (0..10).map(|_foo| do_something()); + (0..=10).map(|_| do_something()); + (3..10).map(|_| do_something()); + (3..=10).map(|_| do_something()); + (0..10).map(|_| 3); + (0..10).map(|_| { + let x = 3; + x + 2 + }); + (0..10).map(|_| do_something()).collect::>(); + let upper = 4; + (0..upper).map(|_| do_something()); + let upper_fn = || 4; + (0..upper_fn()).map(|_| do_something()); + (0..=upper_fn()).map(|_| do_something()); + (2..upper_fn()).map(|_| do_something()); + (2..=upper_fn()).map(|_| do_something()); + (-3..9).map(|_| do_something()); + (9..3).map(|_| do_something()); + (9..=9).map(|_| do_something()); + (1..=1 << 4).map(|_| do_something()); + // These should not be raised + gen!(); + let lower = 2; + let lower_fn = || 2; + (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..=upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (0..10).map(|x| do_something_interesting(x, 4)); // Actual map over range + "Foobar".chars().map(|_| do_something()); // Not a map over range + // i128::MAX == 340282366920938463463374607431768211455 + (0..=340282366920938463463374607431768211455).map(|_: u128| do_something()); // Can't be replaced due to overflow +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.28"] +fn msrv_1_28() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.81"] +fn msrv_1_82() { + (0..10).map(|_| 3); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.stderr b/tests/ui/map_with_unused_argument_over_ranges.stderr new file mode 100644 index 000000000000..0b56c6d95219 --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.stderr @@ -0,0 +1,223 @@ +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:25:5 + | +LL | (0..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-with-unused-argument-over-ranges` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_with_unused_argument_over_ranges)]` +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:26:5 + | +LL | (0..10).map(|_foo| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_foo| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:27:5 + | +LL | (0..=10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..=10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(11); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:28:5 + | +LL | (3..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (3..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(7); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:29:5 + | +LL | (3..=10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (3..=10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(8); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:30:5 + | +LL | (0..10).map(|_| 3); + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_n` + | +LL | std::iter::repeat_n(3, 10); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~ + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:31:5 + | +LL | / (0..10).map(|_| { +LL | | let x = 3; +LL | | x + 2 +LL | | }); + | |______^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL ~ std::iter::repeat_with(|| { +LL | let x = 3; +LL | x + 2 +LL ~ }).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:35:5 + | +LL | (0..10).map(|_| do_something()).collect::>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()).collect::>(); +LL + std::iter::repeat_with(|| do_something()).take(10).collect::>(); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:37:5 + | +LL | (0..upper).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..upper).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:39:5 + | +LL | (0..upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn()); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:40:5 + | +LL | (0..=upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..=upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() + 1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:41:5 + | +LL | (2..upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (2..upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() - 2); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:42:5 + | +LL | (2..=upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (2..=upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() - 1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:44:5 + | +LL | (9..3).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (9..3).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(0); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:45:5 + | +LL | (9..=9).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (9..=9).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:46:5 + | +LL | (1..=1 << 4).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (1..=1 << 4).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:67:5 + | +LL | (0..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:72:5 + | +LL | (0..10).map(|_| 3); + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat` and `take` + | +LL | std::iter::repeat(3).take(10); + | ~~~~~~~~~~~~~~~~~ ~ +++++++++ + +error: aborting due to 18 previous errors + diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 7c8827895115..754fe061c4ae 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -2,7 +2,6 @@ #![allow(incomplete_features, clippy::let_and_return, clippy::missing_transmute_annotations)] #![feature(const_trait_impl, abi_vectorcall)] - use std::mem::transmute; struct Game { diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 48312d48ed3d..460be0733e03 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -2,7 +2,6 @@ #![allow(incomplete_features, clippy::let_and_return, clippy::missing_transmute_annotations)] #![feature(const_trait_impl, abi_vectorcall)] - use std::mem::transmute; struct Game { diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index f28dec5c7f19..d553c5225565 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -1,5 +1,5 @@ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:14:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:13:5 | LL | / pub fn new() -> Self { LL | | @@ -16,7 +16,7 @@ LL | pub const fn new() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:20:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:19:5 | LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { LL | | @@ -30,7 +30,7 @@ LL | const fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:27:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:26:1 | LL | / fn one() -> i32 { LL | | @@ -44,7 +44,7 @@ LL | const fn one() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:33:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:32:1 | LL | / fn two() -> i32 { LL | | @@ -59,7 +59,7 @@ LL | const fn two() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:40:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:39:1 | LL | / fn string() -> String { LL | | @@ -73,7 +73,7 @@ LL | const fn string() -> String { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:46:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:45:1 | LL | / unsafe fn four() -> i32 { LL | | @@ -87,7 +87,7 @@ LL | const unsafe fn four() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:52:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:51:1 | LL | / fn generic(t: T) -> T { LL | | @@ -101,7 +101,7 @@ LL | const fn generic(t: T) -> T { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:61:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:60:1 | LL | / fn generic_arr(t: [T; 1]) -> T { LL | | @@ -115,7 +115,7 @@ LL | const fn generic_arr(t: [T; 1]) -> T { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:75:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:74:9 | LL | / pub fn b(self, a: &A) -> B { LL | | @@ -129,7 +129,7 @@ LL | pub const fn b(self, a: &A) -> B { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:85:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:84:5 | LL | / fn const_fn_stabilized_before_msrv(byte: u8) { LL | | @@ -143,7 +143,7 @@ LL | const fn const_fn_stabilized_before_msrv(byte: u8) { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:97:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:96:1 | LL | / fn msrv_1_46() -> i32 { LL | | @@ -157,7 +157,7 @@ LL | const fn msrv_1_46() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:117:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:116:1 | LL | fn d(this: D) {} | ^^^^^^^^^^^^^^^^ @@ -168,7 +168,7 @@ LL | const fn d(this: D) {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:125:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:124:9 | LL | / fn deref_ptr_can_be_const(self) -> usize { LL | | @@ -182,7 +182,7 @@ LL | const fn deref_ptr_can_be_const(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:130:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:129:9 | LL | / fn deref_copied_val(self) -> usize { LL | | @@ -196,7 +196,7 @@ LL | const fn deref_copied_val(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:141:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:140:5 | LL | / fn union_access_can_be_const() { LL | | @@ -211,7 +211,7 @@ LL | const fn union_access_can_be_const() { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:149:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:148:9 | LL | extern "C" fn c() {} | ^^^^^^^^^^^^^^^^^^^^ @@ -222,7 +222,7 @@ LL | const extern "C" fn c() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:153:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 | LL | extern fn implicit_c() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,7 +233,7 @@ LL | const extern fn implicit_c() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:170:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:169:9 | LL | / pub fn new(strings: Vec) -> Self { LL | | Self { strings } @@ -246,7 +246,7 @@ LL | pub const fn new(strings: Vec) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:175:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:174:9 | LL | / pub fn empty() -> Self { LL | | Self { strings: Vec::new() } @@ -259,7 +259,7 @@ LL | pub const fn empty() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:186:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:185:9 | LL | / pub fn new(text: String) -> Self { LL | | let vec = Vec::new(); @@ -273,7 +273,7 @@ LL | pub const fn new(text: String) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:205:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:204:5 | LL | fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -284,7 +284,7 @@ LL | const fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:209:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:208:5 | LL | extern "C-unwind" fn c_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -295,7 +295,7 @@ LL | const extern "C-unwind" fn c_unwind() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:211:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:210:5 | LL | extern "system" fn system() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -306,7 +306,7 @@ LL | const extern "system" fn system() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:213:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:212:5 | LL | extern "system-unwind" fn system_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -317,7 +317,7 @@ LL | const extern "system-unwind" fn system_unwind() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:215:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:214:5 | LL | pub extern "vectorcall" fn std_call() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -328,7 +328,7 @@ LL | pub const extern "vectorcall" fn std_call() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:217:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:216:5 | LL | pub extern "vectorcall-unwind" fn std_call_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/missing_doc.rs b/tests/ui/missing_doc.rs index 9c936d7fa23e..31bc4309582e 100644 --- a/tests/ui/missing_doc.rs +++ b/tests/ui/missing_doc.rs @@ -116,6 +116,14 @@ with_span!(span pub fn foo_pm() {}); with_span!(span pub static FOO_PM: u32 = 0;); with_span!(span pub const FOO2_PM: u32 = 0;); +// Don't lint unnamed constants +const _: () = (); + +fn issue13298() { + // Rustdoc doesn't generate documentation for items within other items like fns or consts + const MSG: &str = "Hello, world!"; +} + // issue #12197 // Undocumented field originated inside of spanned proc-macro attribute /// Some dox for struct. diff --git a/tests/ui/missing_doc.stderr b/tests/ui/missing_doc.stderr index ef0f96a5b713..133c76ac9d43 100644 --- a/tests/ui/missing_doc.stderr +++ b/tests/ui/missing_doc.stderr @@ -88,5 +88,14 @@ error: missing documentation for a function LL | fn also_undocumented2() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 13 previous errors +error: missing documentation for a function + --> tests/ui/missing_doc.rs:122:1 + | +LL | / fn issue13298() { +LL | | // Rustdoc doesn't generate documentation for items within other items like fns or consts +LL | | const MSG: &str = "Hello, world!"; +LL | | } + | |_^ + +error: aborting due to 14 previous errors diff --git a/tests/ui/needless_as_bytes.fixed b/tests/ui/needless_as_bytes.fixed new file mode 100644 index 000000000000..042342311fdf --- /dev/null +++ b/tests/ui/needless_as_bytes.fixed @@ -0,0 +1,50 @@ +#![warn(clippy::needless_as_bytes)] +#![allow(clippy::const_is_empty)] + +struct S; + +impl S { + fn as_bytes(&self) -> &[u8] { + &[] + } +} + +fn main() { + if "some string".is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".len()); + //~^ needless_as_bytes + } + + let s = String::from("yet another string"); + if s.is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.len()); + //~^ needless_as_bytes + } + + // Do not lint + let _ = S.as_bytes().is_empty(); + let _ = S.as_bytes().len(); + let _ = (&String::new() as &dyn AsBytes).as_bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".as_bytes() + }; + } + m!(1).as_bytes().len(); + m!(2).len(); +} + +pub trait AsBytes { + fn as_bytes(&self) -> &[u8]; +} + +impl AsBytes for String { + fn as_bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.rs b/tests/ui/needless_as_bytes.rs new file mode 100644 index 000000000000..c481e041e0ab --- /dev/null +++ b/tests/ui/needless_as_bytes.rs @@ -0,0 +1,50 @@ +#![warn(clippy::needless_as_bytes)] +#![allow(clippy::const_is_empty)] + +struct S; + +impl S { + fn as_bytes(&self) -> &[u8] { + &[] + } +} + +fn main() { + if "some string".as_bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".as_bytes().len()); + //~^ needless_as_bytes + } + + let s = String::from("yet another string"); + if s.as_bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.as_bytes().len()); + //~^ needless_as_bytes + } + + // Do not lint + let _ = S.as_bytes().is_empty(); + let _ = S.as_bytes().len(); + let _ = (&String::new() as &dyn AsBytes).as_bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".as_bytes() + }; + } + m!(1).as_bytes().len(); + m!(2).len(); +} + +pub trait AsBytes { + fn as_bytes(&self) -> &[u8]; +} + +impl AsBytes for String { + fn as_bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.stderr b/tests/ui/needless_as_bytes.stderr new file mode 100644 index 000000000000..3391238a142b --- /dev/null +++ b/tests/ui/needless_as_bytes.stderr @@ -0,0 +1,29 @@ +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:13:8 + | +LL | if "some string".as_bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `"some string".is_empty()` + | + = note: `-D clippy::needless-as-bytes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_as_bytes)]` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:15:30 + | +LL | println!("len = {}", "some string".as_bytes().len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `"some string".len()` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:20:8 + | +LL | if s.as_bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `s.is_empty()` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:22:30 + | +LL | println!("len = {}", s.as_bytes().len()); + | ^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `s.len()` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/needless_continue.rs b/tests/ui/needless_continue.rs index c26a292c8cb5..b6d8a8f61aeb 100644 --- a/tests/ui/needless_continue.rs +++ b/tests/ui/needless_continue.rs @@ -151,3 +151,20 @@ mod issue_2329 { } } } + +fn issue_13641() { + 'a: while std::hint::black_box(true) { + #[allow(clippy::never_loop)] + loop { + continue 'a; + } + } + + #[allow(clippy::never_loop)] + while std::hint::black_box(true) { + 'b: loop { + continue 'b; + //~^ ERROR: this `continue` expression is redundant + } + } +} diff --git a/tests/ui/needless_continue.stderr b/tests/ui/needless_continue.stderr index ec7c9ba39a72..0741ba692487 100644 --- a/tests/ui/needless_continue.stderr +++ b/tests/ui/needless_continue.stderr @@ -136,5 +136,13 @@ LL | | } println!("bar-5"); } -error: aborting due to 8 previous errors +error: this `continue` expression is redundant + --> tests/ui/needless_continue.rs:166:13 + | +LL | continue 'b; + | ^^^^^^^^^^^^ + | + = help: consider dropping the `continue` expression + +error: aborting due to 9 previous errors diff --git a/tests/ui/no_lints.rs b/tests/ui/no_lints.rs deleted file mode 100644 index a8467bb6ef78..000000000000 --- a/tests/ui/no_lints.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![deny(clippy::all)] - -fn main() {} \ No newline at end of file diff --git a/tests/ui/no_mangle_with_rust_abi.rs b/tests/ui/no_mangle_with_rust_abi.rs index 8c1ea81d2ac9..8be149d18637 100644 --- a/tests/ui/no_mangle_with_rust_abi.rs +++ b/tests/ui/no_mangle_with_rust_abi.rs @@ -2,30 +2,30 @@ #![allow(unused)] #![warn(clippy::no_mangle_with_rust_abi)] -#[no_mangle] +#[unsafe(no_mangle)] fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI //~| NOTE: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` -#[no_mangle] +#[unsafe(no_mangle)] pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI /// # Safety /// This function shouldn't be called unless the horsemen are ready -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI /// # Safety /// This function shouldn't be called unless the horsemen are ready -#[no_mangle] +#[unsafe(no_mangle)] unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI -#[no_mangle] +#[unsafe(no_mangle)] fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( - //~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + //~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI arg_one: u32, arg_two: usize, ) -> u32 { @@ -33,13 +33,13 @@ fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lin } // Must not run on functions that explicitly opt in to using the Rust ABI with `extern "Rust"` -#[no_mangle] +#[unsafe(no_mangle)] #[rustfmt::skip] extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} -#[no_mangle] +#[unsafe(no_mangle)] extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} @@ -48,6 +48,11 @@ extern "C" { fn c_abi_in_block(arg_one: u32, arg_two: usize); } +mod r#fn { + #[unsafe(no_mangle)] + pub(in super::r#fn) fn with_some_fn_around() {} +} + fn main() { // test code goes here } diff --git a/tests/ui/no_mangle_with_rust_abi.stderr b/tests/ui/no_mangle_with_rust_abi.stderr index 27b8b39a21a2..a00ebe5e1ac7 100644 --- a/tests/ui/no_mangle_with_rust_abi.stderr +++ b/tests/ui/no_mangle_with_rust_abi.stderr @@ -1,4 +1,4 @@ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:6:1 | LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} @@ -15,7 +15,7 @@ help: or explicitly set the default LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:11:1 | LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} @@ -30,7 +30,7 @@ help: or explicitly set the default LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:17:1 | LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} @@ -45,7 +45,7 @@ help: or explicitly set the default LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:23:1 | LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} @@ -60,7 +60,7 @@ help: or explicitly set the default LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:27:1 | LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( @@ -79,5 +79,20 @@ help: or explicitly set the default LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( | +++++++++++++ -error: aborting due to 5 previous errors +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi.rs:53:5 + | +LL | pub(in super::r#fn) fn with_some_fn_around() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub(in super::r#fn) extern "C" fn with_some_fn_around() {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub(in super::r#fn) extern "Rust" fn with_some_fn_around() {} + | +++++++++++++ + +error: aborting due to 6 previous errors diff --git a/tests/ui/no_mangle_with_rust_abi_2021.rs b/tests/ui/no_mangle_with_rust_abi_2021.rs new file mode 100644 index 000000000000..c7c973353480 --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi_2021.rs @@ -0,0 +1,57 @@ +//@edition:2021 +// +// Edition 2024 requires the use of #[unsafe(no_mangle)] + +//@no-rustfix: overlapping suggestions +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~| NOTE: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + +#[no_mangle] +pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +#[no_mangle] +fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + //~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to using the Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/no_mangle_with_rust_abi_2021.stderr b/tests/ui/no_mangle_with_rust_abi_2021.stderr new file mode 100644 index 000000000000..15075be72d0b --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi_2021.stderr @@ -0,0 +1,83 @@ +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:10:1 + | +LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::no_mangle_with_rust_abi)]` +help: set an ABI + | +LL | extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:15:1 + | +LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:21:1 + | +LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:27:1 + | +LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:31:1 + | +LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL | | +LL | | arg_one: u32, +LL | | arg_two: usize, +LL | | ) -> u32 { + | |________^ + | +help: set an ABI + | +LL | extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | ++++++++++ +help: or explicitly set the default + | +LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | +++++++++++++ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index eafffdaf8a63..578f918f0139 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -118,25 +118,25 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:161:8 | LL | if !(12 == a) {} - | ^^^^^^^^^^ help: try: `12 != a` + | ^^^^^^^^^^ help: try: `(12 != a)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:162:8 | LL | if !(a == 12) {} - | ^^^^^^^^^^ help: try: `a != 12` + | ^^^^^^^^^^ help: try: `(a != 12)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:163:8 | LL | if !(12 != a) {} - | ^^^^^^^^^^ help: try: `12 == a` + | ^^^^^^^^^^ help: try: `(12 == a)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:164:8 | LL | if !(a != 12) {} - | ^^^^^^^^^^ help: try: `a == 12` + | ^^^^^^^^^^ help: try: `(a == 12)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:168:8 diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index a23310c1ad9f..65ccaaca8918 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -110,9 +110,33 @@ fn dont_warn_for_negated_partial_ord_comparison() { fn issue_12625() { let a = 0; let b = 0; - if (a as u64) < b {} //~ ERROR: this boolean expression can be simplified - if (a as u64) < b {} //~ ERROR: this boolean expression can be simplified - if a as u64 > b {} //~ ERROR: this boolean expression can be simplified + if ((a as u64) < b) {} //~ ERROR: this boolean expression can be simplified + if ((a as u64) < b) {} //~ ERROR: this boolean expression can be simplified + if (a as u64 > b) {} //~ ERROR: this boolean expression can be simplified +} + +fn issue_12761() { + let a = 0; + let b = 0; + let c = 0; + if (a < b) as i32 == c {} //~ ERROR: this boolean expression can be simplified + if (a < b) | (a > c) {} //~ ERROR: this boolean expression can be simplified + let opt: Option = Some(1); + let res: Result = Ok(1); + if res.is_err() as i32 == c {} //~ ERROR: this boolean expression can be simplified + if res.is_err() | opt.is_some() {} //~ ERROR: this boolean expression can be simplified + + fn a(a: bool) -> bool { + (4 <= 3).b() //~ ERROR: this boolean expression can be simplified + } + + trait B { + fn b(&self) -> bool { + true + } + } + + impl B for bool {} } fn issue_13436() { diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 6c844373af70..06db3a1d4a5c 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -115,6 +115,30 @@ fn issue_12625() { if !(a as u64 <= b) {} //~ ERROR: this boolean expression can be simplified } +fn issue_12761() { + let a = 0; + let b = 0; + let c = 0; + if !(a >= b) as i32 == c {} //~ ERROR: this boolean expression can be simplified + if !(a >= b) | !(a <= c) {} //~ ERROR: this boolean expression can be simplified + let opt: Option = Some(1); + let res: Result = Ok(1); + if !res.is_ok() as i32 == c {} //~ ERROR: this boolean expression can be simplified + if !res.is_ok() | !opt.is_none() {} //~ ERROR: this boolean expression can be simplified + + fn a(a: bool) -> bool { + (!(4 > 3)).b() //~ ERROR: this boolean expression can be simplified + } + + trait B { + fn b(&self) -> bool { + true + } + } + + impl B for bool {} +} + fn issue_13436() { fn not_zero(x: i32) -> bool { x != 0 diff --git a/tests/ui/nonminimal_bool_methods.stderr b/tests/ui/nonminimal_bool_methods.stderr index 52803e828aed..66c50f9ff1e1 100644 --- a/tests/ui/nonminimal_bool_methods.stderr +++ b/tests/ui/nonminimal_bool_methods.stderr @@ -83,127 +83,169 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:113:8 | LL | if !(a as u64 >= b) {} - | ^^^^^^^^^^^^^^^^ help: try: `(a as u64) < b` + | ^^^^^^^^^^^^^^^^ help: try: `((a as u64) < b)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:114:8 | LL | if !((a as u64) >= b) {} - | ^^^^^^^^^^^^^^^^^^ help: try: `(a as u64) < b` + | ^^^^^^^^^^^^^^^^^^ help: try: `((a as u64) < b)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:115:8 | LL | if !(a as u64 <= b) {} - | ^^^^^^^^^^^^^^^^ help: try: `a as u64 > b` + | ^^^^^^^^^^^^^^^^ help: try: `(a as u64 > b)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:131:9 + --> tests/ui/nonminimal_bool_methods.rs:122:8 + | +LL | if !(a >= b) as i32 == c {} + | ^^^^^^^^^ help: try: `(a < b)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:123:8 + | +LL | if !(a >= b) | !(a <= c) {} + | ^^^^^^^^^ help: try: `(a < b)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:123:20 + | +LL | if !(a >= b) | !(a <= c) {} + | ^^^^^^^^^ help: try: `(a > c)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:126:8 + | +LL | if !res.is_ok() as i32 == c {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:127:8 + | +LL | if !res.is_ok() | !opt.is_none() {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:127:23 + | +LL | if !res.is_ok() | !opt.is_none() {} + | ^^^^^^^^^^^^^^ help: try: `opt.is_some()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:130:9 + | +LL | (!(4 > 3)).b() + | ^^^^^^^^^^ help: try: `(4 <= 3)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:155:9 | LL | _ = !opt.is_some_and(|x| x < 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x >= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:132:9 + --> tests/ui/nonminimal_bool_methods.rs:156:9 | LL | _ = !opt.is_some_and(|x| x <= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x > 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:133:9 + --> tests/ui/nonminimal_bool_methods.rs:157:9 | LL | _ = !opt.is_some_and(|x| x > 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x <= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:134:9 + --> tests/ui/nonminimal_bool_methods.rs:158:9 | LL | _ = !opt.is_some_and(|x| x >= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x < 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:135:9 + --> tests/ui/nonminimal_bool_methods.rs:159:9 | LL | _ = !opt.is_some_and(|x| x == 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x != 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:136:9 + --> tests/ui/nonminimal_bool_methods.rs:160:9 | LL | _ = !opt.is_some_and(|x| x != 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x == 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:145:9 + --> tests/ui/nonminimal_bool_methods.rs:169:9 | LL | _ = !opt.is_none_or(|x| x < 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x >= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:146:9 + --> tests/ui/nonminimal_bool_methods.rs:170:9 | LL | _ = !opt.is_none_or(|x| x <= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x > 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:147:9 + --> tests/ui/nonminimal_bool_methods.rs:171:9 | LL | _ = !opt.is_none_or(|x| x > 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x <= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:148:9 + --> tests/ui/nonminimal_bool_methods.rs:172:9 | LL | _ = !opt.is_none_or(|x| x >= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x < 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:149:9 + --> tests/ui/nonminimal_bool_methods.rs:173:9 | LL | _ = !opt.is_none_or(|x| x == 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x != 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:150:9 + --> tests/ui/nonminimal_bool_methods.rs:174:9 | LL | _ = !opt.is_none_or(|x| x != 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x == 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:157:9 + --> tests/ui/nonminimal_bool_methods.rs:181:9 | LL | _ = !opt.is_some_and(|x| !x); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:161:9 + --> tests/ui/nonminimal_bool_methods.rs:185:9 | LL | _ = !opt.is_none_or(|x| !x); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:168:9 + --> tests/ui/nonminimal_bool_methods.rs:192:9 | LL | _ = !opt.is_some_and(|x| x.is_ok()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x.is_err())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:169:9 + --> tests/ui/nonminimal_bool_methods.rs:193:9 | LL | _ = !opt.is_some_and(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x.is_ok())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:170:9 + --> tests/ui/nonminimal_bool_methods.rs:194:9 | LL | _ = !opt.is_none_or(|x| x.is_ok()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_err())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:171:9 + --> tests/ui/nonminimal_bool_methods.rs:195:9 | LL | _ = !opt.is_none_or(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_ok())` -error: aborting due to 34 previous errors +error: aborting due to 41 previous errors diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index e03df1658ee3..2ac59718786b 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -54,7 +54,7 @@ #![allow(enum_intrinsics_non_enums)] #![allow(non_fmt_panics)] #![allow(named_arguments_used_positionally)] -#![allow(temporary_cstring_as_ptr)] +#![allow(dangling_pointers_from_temporaries)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index b906079d7df3..a5ae727b7ee8 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,17 +1,11 @@ -error: lint `temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:57:10 - | -LL | #![allow(temporary_cstring_as_ptr)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` - | - = note: `-D renamed-and-removed-lints` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` - error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` --> tests/ui/rename.rs:64:9 @@ -403,5 +397,5 @@ error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_e LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` -error: aborting due to 67 previous errors +error: aborting due to 66 previous errors diff --git a/tests/ui/repeat_vec_with_capacity.fixed b/tests/ui/repeat_vec_with_capacity.fixed index 2afe2f433258..f72b61b5f6cd 100644 --- a/tests/ui/repeat_vec_with_capacity.fixed +++ b/tests/ui/repeat_vec_with_capacity.fixed @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::repeat_vec_with_capacity)] fn main() { diff --git a/tests/ui/repeat_vec_with_capacity.rs b/tests/ui/repeat_vec_with_capacity.rs index 659f2a3953dd..c0cc81f78436 100644 --- a/tests/ui/repeat_vec_with_capacity.rs +++ b/tests/ui/repeat_vec_with_capacity.rs @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::repeat_vec_with_capacity)] fn main() { diff --git a/tests/ui/repeat_vec_with_capacity.stderr b/tests/ui/repeat_vec_with_capacity.stderr index cec9c6ea84a2..43027c9cb892 100644 --- a/tests/ui/repeat_vec_with_capacity.stderr +++ b/tests/ui/repeat_vec_with_capacity.stderr @@ -1,5 +1,5 @@ error: repeating `Vec::with_capacity` using `vec![x; n]`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:5:9 + --> tests/ui/repeat_vec_with_capacity.rs:6:9 | LL | vec![Vec::<()>::with_capacity(42); 123]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL | (0..123).map(|_| Vec::<()>::with_capacity(42)).collect::>(); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: repeating `Vec::with_capacity` using `vec![x; n]`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:11:9 + --> tests/ui/repeat_vec_with_capacity.rs:12:9 | LL | vec![Vec::<()>::with_capacity(42); n]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | (0..n).map(|_| Vec::<()>::with_capacity(42)).collect::>(); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: repeating `Vec::with_capacity` using `iter::repeat`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:26:9 + --> tests/ui/repeat_vec_with_capacity.rs:27:9 | LL | std::iter::repeat(Vec::<()>::with_capacity(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/semicolon_outside_block.fixed b/tests/ui/semicolon_outside_block.fixed index 148e112e0bcb..ac7e86631cae 100644 --- a/tests/ui/semicolon_outside_block.fixed +++ b/tests/ui/semicolon_outside_block.fixed @@ -80,5 +80,13 @@ fn main() { { unit_fn_block(); }; + unsafe { + std::arch::asm!("") + }; + + { + line!() + }; + unit_fn_block() } diff --git a/tests/ui/semicolon_outside_block.rs b/tests/ui/semicolon_outside_block.rs index c767201469ab..68f25339e322 100644 --- a/tests/ui/semicolon_outside_block.rs +++ b/tests/ui/semicolon_outside_block.rs @@ -80,5 +80,13 @@ fn main() { { unit_fn_block(); }; + unsafe { + std::arch::asm!(""); + } + + { + line!(); + } + unit_fn_block() } diff --git a/tests/ui/semicolon_outside_block.stderr b/tests/ui/semicolon_outside_block.stderr index 68b44c8f980f..ff8c00048f63 100644 --- a/tests/ui/semicolon_outside_block.stderr +++ b/tests/ui/semicolon_outside_block.stderr @@ -51,5 +51,33 @@ LL - { m!(()); } LL + { m!(()) }; | -error: aborting due to 4 previous errors +error: consider moving the `;` outside the block for consistent formatting + --> tests/ui/semicolon_outside_block.rs:83:5 + | +LL | / unsafe { +LL | | std::arch::asm!(""); +LL | | } + | |_____^ + | +help: put the `;` here + | +LL ~ std::arch::asm!("") +LL ~ }; + | + +error: consider moving the `;` outside the block for consistent formatting + --> tests/ui/semicolon_outside_block.rs:87:5 + | +LL | / { +LL | | line!(); +LL | | } + | |_____^ + | +help: put the `;` here + | +LL ~ line!() +LL ~ }; + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/suspicious_map.rs b/tests/ui/suspicious_map.rs index d4247fcd9265..d4a52cb110fb 100644 --- a/tests/ui/suspicious_map.rs +++ b/tests/ui/suspicious_map.rs @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::suspicious_map)] fn main() { diff --git a/tests/ui/suspicious_map.stderr b/tests/ui/suspicious_map.stderr index 2a5e2bf2a346..769adebaede7 100644 --- a/tests/ui/suspicious_map.stderr +++ b/tests/ui/suspicious_map.stderr @@ -1,5 +1,5 @@ error: this call to `map()` won't have an effect on the call to `count()` - --> tests/ui/suspicious_map.rs:4:13 + --> tests/ui/suspicious_map.rs:5:13 | LL | let _ = (0..3).map(|x| x + 2).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | let _ = (0..3).map(|x| x + 2).count(); = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` error: this call to `map()` won't have an effect on the call to `count()` - --> tests/ui/suspicious_map.rs:8:13 + --> tests/ui/suspicious_map.rs:9:13 | LL | let _ = (0..3).map(f).count(); | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/unnecessary_filter_map.rs b/tests/ui/unnecessary_filter_map.rs index 1e0d7d12965c..8cf102ab0a55 100644 --- a/tests/ui/unnecessary_filter_map.rs +++ b/tests/ui/unnecessary_filter_map.rs @@ -1,26 +1,31 @@ +//@no-rustfix #![allow(dead_code)] fn main() { let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply //~| NOTE: `-D clippy::unnecessary-filter-map` implied by `-D warnings` let _ = (0..4).filter_map(|x| { - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply if x > 1 { return Some(x); }; None }); let _ = (0..4).filter_map(|x| match x { - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply 0 | 1 => None, _ => Some(x), }); let _ = (0..4).filter_map(|x| Some(x + 1)); - //~^ ERROR: this `.filter_map` can be written more simply using `.map` + //~^ ERROR: this `.filter_map` can be written more simply let _ = (0..4).filter_map(i32::checked_abs); + + let _ = (0..4).filter_map(Some); + + let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); } fn filter_map_none_changes_item_type() -> impl Iterator { diff --git a/tests/ui/unnecessary_filter_map.stderr b/tests/ui/unnecessary_filter_map.stderr index f32d444ba9e0..b21589c5f844 100644 --- a/tests/ui/unnecessary_filter_map.stderr +++ b/tests/ui/unnecessary_filter_map.stderr @@ -1,14 +1,14 @@ -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:4:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:5:13 | LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `filter` | = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_filter_map)]` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:7:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:8:13 | LL | let _ = (0..4).filter_map(|x| { | _____________^ @@ -18,10 +18,10 @@ LL | | return Some(x); LL | | }; LL | | None LL | | }); - | |______^ + | |______^ help: try instead: `filter` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:14:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:15:13 | LL | let _ = (0..4).filter_map(|x| match x { | _____________^ @@ -29,19 +29,40 @@ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), LL | | }); - | |______^ + | |______^ help: try instead: `filter` -error: this `.filter_map` can be written more simply using `.map` - --> tests/ui/unnecessary_filter_map.rs:20:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:21:13 | LL | let _ = (0..4).filter_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:160:14 +error: redundant closure + --> tests/ui/unnecessary_filter_map.rs:28:57 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^^^^^^^^ help: replace the closure with the function itself: `Some` + | + = note: `-D clippy::redundant-closure` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]` + +error: filter_map is unnecessary + --> tests/ui/unnecessary_filter_map.rs:28:61 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^ help: try removing the filter_map + +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:28:13 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map` + +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:165:14 | LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `filter` -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs index 9972b68092ab..c357d8532481 100644 --- a/tests/ui/unnecessary_find_map.rs +++ b/tests/ui/unnecessary_find_map.rs @@ -1,24 +1,25 @@ +//@no-rustfix #![allow(dead_code)] fn main() { let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply //~| NOTE: `-D clippy::unnecessary-find-map` implied by `-D warnings` let _ = (0..4).find_map(|x| { - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply if x > 1 { return Some(x); }; None }); let _ = (0..4).find_map(|x| match x { - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply 0 | 1 => None, _ => Some(x), }); let _ = (0..4).find_map(|x| Some(x + 1)); - //~^ ERROR: this `.find_map` can be written more simply using `.map(..).next()` + //~^ ERROR: this `.find_map` can be written more simply let _ = (0..4).find_map(i32::checked_abs); } diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr index bb939a992146..98a6c3d164a6 100644 --- a/tests/ui/unnecessary_find_map.stderr +++ b/tests/ui/unnecessary_find_map.stderr @@ -1,14 +1,14 @@ -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:4:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:5:13 | LL | let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `find` | = note: `-D clippy::unnecessary-find-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_find_map)]` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:7:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:8:13 | LL | let _ = (0..4).find_map(|x| { | _____________^ @@ -18,10 +18,10 @@ LL | | return Some(x); LL | | }; LL | | None LL | | }); - | |______^ + | |______^ help: try instead: `find` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:14:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:15:13 | LL | let _ = (0..4).find_map(|x| match x { | _____________^ @@ -29,19 +29,19 @@ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), LL | | }); - | |______^ + | |______^ help: try instead: `find` -error: this `.find_map` can be written more simply using `.map(..).next()` - --> tests/ui/unnecessary_find_map.rs:20:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:21:13 | LL | let _ = (0..4).find_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map(..).next()` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:32:14 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:33:14 | LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `find` error: aborting due to 5 previous errors diff --git a/tests/ui/unused_io_amount.rs b/tests/ui/unused_io_amount.rs index f5b200d5ffed..175c4ca76895 100644 --- a/tests/ui/unused_io_amount.rs +++ b/tests/ui/unused_io_amount.rs @@ -277,4 +277,18 @@ fn allow_works(mut f: F) { f.read(&mut data).unwrap(); } +struct Reader {} + +impl Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + todo!() + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + // We shouldn't recommend using Read::read_exact inside Read::read_exact! + self.read(buf).unwrap(); + Ok(()) + } +} + fn main() {} diff --git a/triagebot.toml b/triagebot.toml index cd9641eedd8f..eadfd7107c77 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -21,13 +21,11 @@ new_pr = true contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ "matthiaskrgr", - "giraffate", ] [assign.owners] "/.github" = ["@flip1995"] "/book" = ["@flip1995"] -"/util/gh-pages" = ["@xFrednet"] "*" = [ "@Manishearth", "@llogiq", diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 2412f0fd1813..deb0ef0b4996 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -52,12 +52,12 @@ Otherwise, have a great day =^.^= {# #}
{# #} -
{# #} +