diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 4be363ca9a2b0..2eecf81a5d28f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1708,15 +1708,32 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let mut cs_bx = Bx::build(self.cx, llbb); let cs = cs_bx.catch_switch(None, None, &[cp_llbb]); - // The "null" here is actually a RTTI type descriptor for the - // C++ personality function, but `catch (...)` has no type so - // it's null. The 64 here is actually a bitfield which - // represents that this is a catch-all block. bx = Bx::build(self.cx, cp_llbb); let null = bx.const_null(bx.type_ptr_ext(bx.cx().data_layout().instruction_address_space)); - let sixty_four = bx.const_i32(64); - funclet = Some(bx.catch_pad(cs, &[null, sixty_four, null])); + + // The `null` in first argument here is actually a RTTI type + // descriptor for the C++ personality function, but `catch (...)` + // has no type so it's null. + let args = if base::wants_msvc_seh(self.cx.sess()) { + // This bitmask is a single `HT_IsStdDotDot` flag, which + // represents that this is a C++-style `catch (...)` block that + // only captures programmatic exceptions, not all SEH + // exceptions. The second `null` points to a non-existent + // `alloca` instruction, which an LLVM pass would inline into + // the initial SEH frame allocation. + let adjectives = bx.const_i32(0x40); + &[null, adjectives, null] as &[_] + } else { + // Specifying more arguments than necessary usually doesn't + // hurt, but the `WasmEHPrepare` LLVM pass does not recognize + // anything other than a single `null` as a `catch (...)` block, + // leading to problems down the line during instruction + // selection. + &[null] as &[_] + }; + + funclet = Some(bx.catch_pad(cs, args)); } else { llbb = Bx::append_block(self.cx, self.llfn, "terminate"); bx = Bx::build(self.cx, llbb); diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index bc7cd3d118c5a..723b894c43bc4 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -246,6 +246,14 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } } ast_visit::walk_assoc_item(cx, item, ctxt); + match ctxt { + ast_visit::AssocCtxt::Trait => { + lint_callback!(cx, check_trait_item_post, item); + } + ast_visit::AssocCtxt::Impl => { + lint_callback!(cx, check_impl_item_post, item); + } + } }); } diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs index 77bd13aacf737..409a23d1da039 100644 --- a/compiler/rustc_lint/src/passes.rs +++ b/compiler/rustc_lint/src/passes.rs @@ -162,7 +162,9 @@ macro_rules! early_lint_methods { c: rustc_span::Span, d_: rustc_ast::NodeId); fn check_trait_item(a: &rustc_ast::AssocItem); + fn check_trait_item_post(a: &rustc_ast::AssocItem); fn check_impl_item(a: &rustc_ast::AssocItem); + fn check_impl_item_post(a: &rustc_ast::AssocItem); fn check_variant(a: &rustc_ast::Variant); fn check_attribute(a: &rustc_ast::Attribute); fn check_attributes(a: &[rustc_ast::Attribute]); diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index c7d6d8a55c2e3..07c75677d0532 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -965,8 +965,9 @@ impl Default for Rc { /// This may or may not share an allocation with other Rcs on the same thread. #[inline] fn default() -> Self { - let c_str: &CStr = Default::default(); - Rc::from(c_str) + let rc = Rc::<[u8]>::from(*b"\0"); + // `[u8]` has the same layout as `CStr`, and it is `NUL` terminated. + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr) } } } diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index ae3318b839dd7..05e76ccf685e1 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -2369,7 +2369,9 @@ impl Default for Rc { /// This may or may not share an allocation with other Rcs on the same thread. #[inline] fn default() -> Self { - Rc::from("") + let rc = Rc::<[u8]>::default(); + // `[u8]` has the same layout as `str`. + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const str) } } } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 28f16da1ed8d2..e3810030dedf7 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -14,7 +14,7 @@ use crate::os::unix::fs::symlink as junction_point; use crate::os::windows::fs::{OpenOptionsExt, junction_point, symlink_dir, symlink_file}; use crate::path::Path; use crate::sync::Arc; -use crate::sys_common::io::test::{TempDir, tmpdir}; +use crate::test_helpers::{TempDir, tmpdir}; use crate::time::{Duration, Instant, SystemTime}; use crate::{env, str, thread}; diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index cfd03b8e3d6d0..0ffad2c27a4d5 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -344,7 +344,7 @@ pub mod prelude; mod stdio; mod util; -const DEFAULT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +const DEFAULT_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; pub(crate) use stdio::cleanup; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 7c18226874cc8..954a4182fbd6c 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -739,27 +739,4 @@ mod sealed { #[cfg(test)] #[allow(dead_code)] // Not used in all configurations. -pub(crate) mod test_helpers { - /// Test-only replacement for `rand::thread_rng()`, which is unusable for - /// us, as we want to allow running stdlib tests on tier-3 targets which may - /// not have `getrandom` support. - /// - /// Does a bit of a song and dance to ensure that the seed is different on - /// each call (as some tests sadly rely on this), but doesn't try that hard. - /// - /// This is duplicated in the `core`, `alloc` test suites (as well as - /// `std`'s integration tests), but figuring out a mechanism to share these - /// seems far more painful than copy-pasting a 7 line function a couple - /// times, given that even under a perma-unstable feature, I don't think we - /// want to expose types from `rand` from `std`. - #[track_caller] - pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { - use core::hash::{BuildHasher, Hash, Hasher}; - let mut hasher = crate::hash::RandomState::new().build_hasher(); - core::panic::Location::caller().hash(&mut hasher); - let hc64 = hasher.finish(); - let seed_vec = hc64.to_le_bytes().into_iter().chain(0u8..8).collect::>(); - let seed: [u8; 16] = seed_vec.as_slice().try_into().unwrap(); - rand::SeedableRng::from_seed(seed) - } -} +pub(crate) mod test_helpers; diff --git a/library/std/src/os/unix/fs/tests.rs b/library/std/src/os/unix/fs/tests.rs index 67f607bd46837..db9621c8c205c 100644 --- a/library/std/src/os/unix/fs/tests.rs +++ b/library/std/src/os/unix/fs/tests.rs @@ -3,7 +3,7 @@ use super::*; #[test] fn read_vectored_at() { let msg = b"preadv is working!"; - let dir = crate::sys_common::io::test::tmpdir(); + let dir = crate::test_helpers::tmpdir(); let filename = dir.join("preadv.txt"); { @@ -31,7 +31,7 @@ fn read_vectored_at() { #[test] fn write_vectored_at() { let msg = b"pwritev is not working!"; - let dir = crate::sys_common::io::test::tmpdir(); + let dir = crate::test_helpers::tmpdir(); let filename = dir.join("preadv.txt"); { diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index 21e2176185d25..0398a535eb54a 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -7,7 +7,7 @@ use crate::os::android::net::{SocketAddrExt, UnixSocketExt}; use crate::os::linux::net::{SocketAddrExt, UnixSocketExt}; #[cfg(any(target_os = "android", target_os = "linux"))] use crate::os::unix::io::AsRawFd; -use crate::sys_common::io::test::tmpdir; +use crate::test_helpers::tmpdir; use crate::thread; use crate::time::Duration; diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 9b87259abefc1..69273d863ebbd 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -549,7 +549,7 @@ fn debug_print() { #[test] #[cfg(windows)] fn run_bat_script() { - let tempdir = crate::sys_common::io::test::tmpdir(); + let tempdir = crate::test_helpers::tmpdir(); let script_path = tempdir.join("hello.cmd"); crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap(); @@ -568,7 +568,7 @@ fn run_bat_script() { #[test] #[cfg(windows)] fn run_canonical_bat_script() { - let tempdir = crate::sys_common::io::test::tmpdir(); + let tempdir = crate::test_helpers::tmpdir(); let script_path = tempdir.join("hello.cmd"); crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap(); diff --git a/library/std/src/sys/pal/hermit/io.rs b/library/std/src/sys/io/io_slice/iovec.rs similarity index 90% rename from library/std/src/sys/pal/hermit/io.rs rename to library/std/src/sys/io/io_slice/iovec.rs index 0424a1ac55a29..072191315f7c5 100644 --- a/library/std/src/sys/pal/hermit/io.rs +++ b/library/std/src/sys/io/io_slice/iovec.rs @@ -1,8 +1,13 @@ -use hermit_abi::{c_void, iovec}; +#[cfg(target_os = "hermit")] +use hermit_abi::iovec; +#[cfg(target_family = "unix")] +use libc::iovec; +use crate::ffi::c_void; use crate::marker::PhantomData; -use crate::os::hermit::io::{AsFd, AsRawFd}; use crate::slice; +#[cfg(target_os = "solid_asp3")] +use crate::sys::pal::abi::sockets::iovec; #[derive(Copy, Clone)] #[repr(transparent)] @@ -80,8 +85,3 @@ impl<'a> IoSliceMut<'a> { unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } } } - -pub fn is_terminal(fd: &impl AsFd) -> bool { - let fd = fd.as_fd(); - hermit_abi::isatty(fd.as_raw_fd()) -} diff --git a/library/std/src/sys/pal/unsupported/io.rs b/library/std/src/sys/io/io_slice/unsupported.rs similarity index 94% rename from library/std/src/sys/pal/unsupported/io.rs rename to library/std/src/sys/io/io_slice/unsupported.rs index 604735d32d51a..1572cac6cd771 100644 --- a/library/std/src/sys/pal/unsupported/io.rs +++ b/library/std/src/sys/io/io_slice/unsupported.rs @@ -50,7 +50,3 @@ impl<'a> IoSliceMut<'a> { self.0 } } - -pub fn is_terminal(_: &T) -> bool { - false -} diff --git a/library/std/src/sys/pal/wasi/io.rs b/library/std/src/sys/io/io_slice/wasi.rs similarity index 90% rename from library/std/src/sys/pal/wasi/io.rs rename to library/std/src/sys/io/io_slice/wasi.rs index 57f81bc6257cd..87acbbd924e56 100644 --- a/library/std/src/sys/pal/wasi/io.rs +++ b/library/std/src/sys/io/io_slice/wasi.rs @@ -1,7 +1,4 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - use crate::marker::PhantomData; -use crate::os::fd::{AsFd, AsRawFd}; use crate::slice; #[derive(Copy, Clone)] @@ -77,8 +74,3 @@ impl<'a> IoSliceMut<'a> { unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } } } - -pub fn is_terminal(fd: &impl AsFd) -> bool { - let fd = fd.as_fd(); - unsafe { libc::isatty(fd.as_raw_fd()) != 0 } -} diff --git a/library/std/src/sys/pal/solid/io.rs b/library/std/src/sys/io/io_slice/windows.rs similarity index 54% rename from library/std/src/sys/pal/solid/io.rs rename to library/std/src/sys/io/io_slice/windows.rs index 9ef4b7049b690..c3d8ec87c19e3 100644 --- a/library/std/src/sys/pal/solid/io.rs +++ b/library/std/src/sys/io/io_slice/windows.rs @@ -1,86 +1,82 @@ -use libc::c_void; - -use super::abi::sockets::iovec; use crate::marker::PhantomData; use crate::slice; +use crate::sys::c; #[derive(Copy, Clone)] #[repr(transparent)] pub struct IoSlice<'a> { - vec: iovec, + vec: c::WSABUF, _p: PhantomData<&'a [u8]>, } impl<'a> IoSlice<'a> { #[inline] pub fn new(buf: &'a [u8]) -> IoSlice<'a> { + assert!(buf.len() <= u32::MAX as usize); IoSlice { - vec: iovec { iov_base: buf.as_ptr() as *mut u8 as *mut c_void, iov_len: buf.len() }, + vec: c::WSABUF { len: buf.len() as u32, buf: buf.as_ptr() as *mut u8 }, _p: PhantomData, } } #[inline] pub fn advance(&mut self, n: usize) { - if self.vec.iov_len < n { + if (self.vec.len as usize) < n { panic!("advancing IoSlice beyond its length"); } unsafe { - self.vec.iov_len -= n; - self.vec.iov_base = self.vec.iov_base.add(n); + self.vec.len -= n as u32; + self.vec.buf = self.vec.buf.add(n); } } #[inline] pub const fn as_slice(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) } + unsafe { slice::from_raw_parts(self.vec.buf, self.vec.len as usize) } } } #[repr(transparent)] pub struct IoSliceMut<'a> { - vec: iovec, + vec: c::WSABUF, _p: PhantomData<&'a mut [u8]>, } impl<'a> IoSliceMut<'a> { #[inline] pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { + assert!(buf.len() <= u32::MAX as usize); IoSliceMut { - vec: iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len() }, + vec: c::WSABUF { len: buf.len() as u32, buf: buf.as_mut_ptr() }, _p: PhantomData, } } #[inline] pub fn advance(&mut self, n: usize) { - if self.vec.iov_len < n { + if (self.vec.len as usize) < n { panic!("advancing IoSliceMut beyond its length"); } unsafe { - self.vec.iov_len -= n; - self.vec.iov_base = self.vec.iov_base.add(n); + self.vec.len -= n as u32; + self.vec.buf = self.vec.buf.add(n); } } #[inline] pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) } + unsafe { slice::from_raw_parts(self.vec.buf, self.vec.len as usize) } } #[inline] pub const fn into_slice(self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } + unsafe { slice::from_raw_parts_mut(self.vec.buf, self.vec.len as usize) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } + unsafe { slice::from_raw_parts_mut(self.vec.buf, self.vec.len as usize) } } } - -pub fn is_terminal(_: &T) -> bool { - false -} diff --git a/library/std/src/sys/io/is_terminal/hermit.rs b/library/std/src/sys/io/is_terminal/hermit.rs new file mode 100644 index 0000000000000..61bdb6f0a5440 --- /dev/null +++ b/library/std/src/sys/io/is_terminal/hermit.rs @@ -0,0 +1,6 @@ +use crate::os::fd::{AsFd, AsRawFd}; + +pub fn is_terminal(fd: &impl AsFd) -> bool { + let fd = fd.as_fd(); + hermit_abi::isatty(fd.as_raw_fd()) +} diff --git a/library/std/src/sys/io/is_terminal/isatty.rs b/library/std/src/sys/io/is_terminal/isatty.rs new file mode 100644 index 0000000000000..6e0b46211b907 --- /dev/null +++ b/library/std/src/sys/io/is_terminal/isatty.rs @@ -0,0 +1,6 @@ +use crate::os::fd::{AsFd, AsRawFd}; + +pub fn is_terminal(fd: &impl AsFd) -> bool { + let fd = fd.as_fd(); + unsafe { libc::isatty(fd.as_raw_fd()) != 0 } +} diff --git a/library/std/src/sys/io/is_terminal/unsupported.rs b/library/std/src/sys/io/is_terminal/unsupported.rs new file mode 100644 index 0000000000000..cee4add32fbfc --- /dev/null +++ b/library/std/src/sys/io/is_terminal/unsupported.rs @@ -0,0 +1,3 @@ +pub fn is_terminal(_: &T) -> bool { + false +} diff --git a/library/std/src/sys/pal/windows/io.rs b/library/std/src/sys/io/is_terminal/windows.rs similarity index 55% rename from library/std/src/sys/pal/windows/io.rs rename to library/std/src/sys/io/is_terminal/windows.rs index f2865d2ffc168..3ec18fb47b9de 100644 --- a/library/std/src/sys/pal/windows/io.rs +++ b/library/std/src/sys/io/is_terminal/windows.rs @@ -1,90 +1,8 @@ -use core::ffi::c_void; - -use crate::marker::PhantomData; +use crate::ffi::c_void; use crate::mem::size_of; use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; -use crate::slice; use crate::sys::c; -#[derive(Copy, Clone)] -#[repr(transparent)] -pub struct IoSlice<'a> { - vec: c::WSABUF, - _p: PhantomData<&'a [u8]>, -} - -impl<'a> IoSlice<'a> { - #[inline] - pub fn new(buf: &'a [u8]) -> IoSlice<'a> { - assert!(buf.len() <= u32::MAX as usize); - IoSlice { - vec: c::WSABUF { len: buf.len() as u32, buf: buf.as_ptr() as *mut u8 }, - _p: PhantomData, - } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if (self.vec.len as usize) < n { - panic!("advancing IoSlice beyond its length"); - } - - unsafe { - self.vec.len -= n as u32; - self.vec.buf = self.vec.buf.add(n); - } - } - - #[inline] - pub const fn as_slice(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.vec.buf, self.vec.len as usize) } - } -} - -#[repr(transparent)] -pub struct IoSliceMut<'a> { - vec: c::WSABUF, - _p: PhantomData<&'a mut [u8]>, -} - -impl<'a> IoSliceMut<'a> { - #[inline] - pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { - assert!(buf.len() <= u32::MAX as usize); - IoSliceMut { - vec: c::WSABUF { len: buf.len() as u32, buf: buf.as_mut_ptr() }, - _p: PhantomData, - } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if (self.vec.len as usize) < n { - panic!("advancing IoSliceMut beyond its length"); - } - - unsafe { - self.vec.len -= n as u32; - self.vec.buf = self.vec.buf.add(n); - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf, self.vec.len as usize) } - } - - #[inline] - pub const fn into_slice(self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf, self.vec.len as usize) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf, self.vec.len as usize) } - } -} - pub fn is_terminal(h: &impl AsHandle) -> bool { handle_is_console(h.as_handle()) } diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs new file mode 100644 index 0000000000000..e00b479109f39 --- /dev/null +++ b/library/std/src/sys/io/mod.rs @@ -0,0 +1,44 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +mod io_slice { + cfg_if::cfg_if! { + if #[cfg(any(target_family = "unix", target_os = "hermit", target_os = "solid_asp3"))] { + mod iovec; + pub use iovec::*; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub use windows::*; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + pub use wasi::*; + } else { + mod unsupported; + pub use unsupported::*; + } + } +} + +mod is_terminal { + cfg_if::cfg_if! { + if #[cfg(any(target_family = "unix", target_os = "wasi"))] { + mod isatty; + pub use isatty::*; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub use windows::*; + } else if #[cfg(target_os = "hermit")] { + mod hermit; + pub use hermit::*; + } else { + mod unsupported; + pub use unsupported::*; + } + } +} + +pub use io_slice::{IoSlice, IoSliceMut}; +pub use is_terminal::is_terminal; + +// Bare metal platforms usually have very small amounts of RAM +// (in the order of hundreds of KB) +pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { 512 } else { 8 * 1024 }; diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 39a0bc6e337c6..1032fcba5e2e1 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -12,6 +12,7 @@ pub mod anonymous_pipe; pub mod backtrace; pub mod cmath; pub mod exit_guard; +pub mod io; pub mod net; pub mod os_str; pub mod path; diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 032007aa4dca5..3d555ad5050c2 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -23,7 +23,6 @@ pub mod env; pub mod fd; pub mod fs; pub mod futex; -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 0f5935d0c7184..9a04fa4b97e19 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -14,8 +14,6 @@ pub mod env; pub mod fd; #[path = "../unsupported/fs.rs"] pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; mod libunwind_integration; pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/sgx/stdio.rs b/library/std/src/sys/pal/sgx/stdio.rs index 2e680e740fde3..e79a3d971c6be 100644 --- a/library/std/src/sys/pal/sgx/stdio.rs +++ b/library/std/src/sys/pal/sgx/stdio.rs @@ -62,7 +62,7 @@ impl io::Write for Stderr { } } -pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; pub fn is_ebadf(err: &io::Error) -> bool { // FIXME: Rust normally maps Unix EBADF to `Uncategorized` diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index caf848a4e9b07..06af7bfade059 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -23,7 +23,6 @@ pub mod env; // `crate::sys::error` pub(crate) mod error; pub mod fs; -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index a9904e66664e3..f850fefc8f22f 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -13,8 +13,6 @@ pub mod env; //pub mod fd; #[path = "../unsupported/fs.rs"] pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index 07025a304bfd7..4766e2ef0a95f 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -17,8 +17,6 @@ pub mod args; pub mod env; pub mod fs; pub mod helpers; -#[path = "../unsupported/io.rs"] -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/unix/io.rs b/library/std/src/sys/pal/unix/io.rs deleted file mode 100644 index 0d5a152dc0dc6..0000000000000 --- a/library/std/src/sys/pal/unix/io.rs +++ /dev/null @@ -1,87 +0,0 @@ -use libc::{c_void, iovec}; - -use crate::marker::PhantomData; -use crate::os::fd::{AsFd, AsRawFd}; -use crate::slice; - -#[derive(Copy, Clone)] -#[repr(transparent)] -pub struct IoSlice<'a> { - vec: iovec, - _p: PhantomData<&'a [u8]>, -} - -impl<'a> IoSlice<'a> { - #[inline] - pub fn new(buf: &'a [u8]) -> IoSlice<'a> { - IoSlice { - vec: iovec { iov_base: buf.as_ptr() as *mut u8 as *mut c_void, iov_len: buf.len() }, - _p: PhantomData, - } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if self.vec.iov_len < n { - panic!("advancing IoSlice beyond its length"); - } - - unsafe { - self.vec.iov_len -= n; - self.vec.iov_base = self.vec.iov_base.add(n); - } - } - - #[inline] - pub const fn as_slice(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) } - } -} - -#[repr(transparent)] -pub struct IoSliceMut<'a> { - vec: iovec, - _p: PhantomData<&'a mut [u8]>, -} - -impl<'a> IoSliceMut<'a> { - #[inline] - pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { - IoSliceMut { - vec: iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len() }, - _p: PhantomData, - } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if self.vec.iov_len < n { - panic!("advancing IoSliceMut beyond its length"); - } - - unsafe { - self.vec.iov_len -= n; - self.vec.iov_base = self.vec.iov_base.add(n); - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) } - } - - #[inline] - pub const fn into_slice(self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } - } -} - -pub fn is_terminal(fd: &impl AsFd) -> bool { - let fd = fd.as_fd(); - unsafe { libc::isatty(fd.as_raw_fd()) != 0 } -} diff --git a/library/std/src/sys/pal/unix/kernel_copy/tests.rs b/library/std/src/sys/pal/unix/kernel_copy/tests.rs index 1350d743ff6f3..54d8f8ed2edd4 100644 --- a/library/std/src/sys/pal/unix/kernel_copy/tests.rs +++ b/library/std/src/sys/pal/unix/kernel_copy/tests.rs @@ -2,7 +2,7 @@ use crate::fs::OpenOptions; use crate::io; use crate::io::{BufRead, Read, Result, Seek, SeekFrom, Write}; use crate::os::unix::io::AsRawFd; -use crate::sys_common::io::test::tmpdir; +use crate::test_helpers::tmpdir; #[test] fn copy_specialization() -> Result<()> { diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 6862399b9425c..027df6c5691c6 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -11,7 +11,6 @@ pub mod env; pub mod fd; pub mod fs; pub mod futex; -pub mod io; #[cfg(any(target_os = "linux", target_os = "android"))] pub mod kernel_copy; #[cfg(target_os = "linux")] diff --git a/library/std/src/sys/pal/unix/stdio.rs b/library/std/src/sys/pal/unix/stdio.rs index 97e75f1b5b669..8c2f61a40de3b 100644 --- a/library/std/src/sys/pal/unix/stdio.rs +++ b/library/std/src/sys/pal/unix/stdio.rs @@ -92,7 +92,7 @@ pub fn is_ebadf(err: &io::Error) -> bool { err.raw_os_error() == Some(libc::EBADF as i32) } -pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; pub fn panic_output() -> Option { Some(Stderr::new()) diff --git a/library/std/src/sys/pal/unsupported/mod.rs b/library/std/src/sys/pal/unsupported/mod.rs index 4941dd4918c86..b1aaeb1b4c814 100644 --- a/library/std/src/sys/pal/unsupported/mod.rs +++ b/library/std/src/sys/pal/unsupported/mod.rs @@ -3,7 +3,6 @@ pub mod args; pub mod env; pub mod fs; -pub mod io; pub mod os; pub mod pipe; pub mod process; diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index 312ad28ab51c8..f4588a60ea9a4 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -20,7 +20,6 @@ pub mod fs; #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/wasi/stdio.rs b/library/std/src/sys/pal/wasi/stdio.rs index ca49f871e1957..d08b772e5fc7d 100644 --- a/library/std/src/sys/pal/wasi/stdio.rs +++ b/library/std/src/sys/pal/wasi/stdio.rs @@ -101,7 +101,7 @@ impl io::Write for Stderr { } } -pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; pub fn is_ebadf(err: &io::Error) -> bool { err.raw_os_error() == Some(wasi::ERRNO_BADF.raw().into()) diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index 234e946d3b8ca..72c9742b2e549 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -17,8 +17,6 @@ pub mod fs; #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; -#[path = "../wasi/io.rs"] -pub mod io; #[path = "../wasi/os.rs"] pub mod os; diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index 1280f3532001a..32d59c4d0f7c4 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -21,8 +21,6 @@ pub mod args; pub mod env; #[path = "../unsupported/fs.rs"] pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; #[path = "../unsupported/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index f9aa049ca9ae6..1eca346b76c2b 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -21,7 +21,6 @@ pub mod fs; #[cfg(not(target_vendor = "win7"))] pub mod futex; pub mod handle; -pub mod io; pub mod os; pub mod pipe; pub mod process; diff --git a/library/std/src/sys/pal/windows/process/tests.rs b/library/std/src/sys/pal/windows/process/tests.rs index 1bcc5fa6b2048..9a1eaf42fd9a8 100644 --- a/library/std/src/sys/pal/windows/process/tests.rs +++ b/library/std/src/sys/pal/windows/process/tests.rs @@ -158,7 +158,7 @@ fn windows_exe_resolver() { use super::resolve_exe; use crate::io; use crate::sys::fs::symlink; - use crate::sys_common::io::test::tmpdir; + use crate::test_helpers::tmpdir; let env_paths = || env::var_os("PATH"); diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 8ba2b6e2f20fd..1bd0e67f37162 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -5,8 +5,6 @@ pub mod args; pub mod env; #[path = "../unsupported/fs.rs"] pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs index 9e9ae86107033..054c867f90d8e 100644 --- a/library/std/src/sys/pal/zkvm/mod.rs +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -16,8 +16,6 @@ pub mod args; pub mod env; #[path = "../unsupported/fs.rs"] pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/zkvm/stdio.rs b/library/std/src/sys/pal/zkvm/stdio.rs index dd218c8894ca5..5f1d06dd1d78d 100644 --- a/library/std/src/sys/pal/zkvm/stdio.rs +++ b/library/std/src/sys/pal/zkvm/stdio.rs @@ -54,7 +54,7 @@ impl io::Write for Stderr { } } -pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; pub fn is_ebadf(_err: &io::Error) -> bool { true diff --git a/library/std/src/sys_common/io.rs b/library/std/src/sys_common/io.rs deleted file mode 100644 index 6f6f282d432d6..0000000000000 --- a/library/std/src/sys_common/io.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Bare metal platforms usually have very small amounts of RAM -// (in the order of hundreds of KB) -pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { 512 } else { 8 * 1024 }; - -#[cfg(test)] -#[allow(dead_code)] // not used on emscripten and wasi -pub mod test { - use rand::RngCore; - - use crate::path::{Path, PathBuf}; - use crate::{env, fs, thread}; - - pub struct TempDir(PathBuf); - - impl TempDir { - pub fn join(&self, path: &str) -> PathBuf { - let TempDir(ref p) = *self; - p.join(path) - } - - pub fn path(&self) -> &Path { - let TempDir(ref p) = *self; - p - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - // Gee, seeing how we're testing the fs module I sure hope that we - // at least implement this correctly! - let TempDir(ref p) = *self; - let result = fs::remove_dir_all(p); - // Avoid panicking while panicking as this causes the process to - // immediately abort, without displaying test results. - if !thread::panicking() { - result.unwrap(); - } - } - } - - #[track_caller] // for `test_rng` - pub fn tmpdir() -> TempDir { - let p = env::temp_dir(); - let mut r = crate::test_helpers::test_rng(); - let ret = p.join(&format!("rust-{}", r.next_u32())); - fs::create_dir(&ret).unwrap(); - TempDir(ret) - } -} diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index 959fbd4ca0a11..4dc67d26bd8ff 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -21,7 +21,6 @@ mod tests; pub mod fs; -pub mod io; pub mod process; pub mod wstr; pub mod wtf8; diff --git a/library/std/src/test_helpers.rs b/library/std/src/test_helpers.rs new file mode 100644 index 0000000000000..7c20f38c863b6 --- /dev/null +++ b/library/std/src/test_helpers.rs @@ -0,0 +1,65 @@ +use rand::{RngCore, SeedableRng}; + +use crate::hash::{BuildHasher, Hash, Hasher, RandomState}; +use crate::panic::Location; +use crate::path::{Path, PathBuf}; +use crate::{env, fs, thread}; + +/// Test-only replacement for `rand::thread_rng()`, which is unusable for +/// us, as we want to allow running stdlib tests on tier-3 targets which may +/// not have `getrandom` support. +/// +/// Does a bit of a song and dance to ensure that the seed is different on +/// each call (as some tests sadly rely on this), but doesn't try that hard. +/// +/// This is duplicated in the `core`, `alloc` test suites (as well as +/// `std`'s integration tests), but figuring out a mechanism to share these +/// seems far more painful than copy-pasting a 7 line function a couple +/// times, given that even under a perma-unstable feature, I don't think we +/// want to expose types from `rand` from `std`. +#[track_caller] +pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { + let mut hasher = RandomState::new().build_hasher(); + Location::caller().hash(&mut hasher); + let hc64 = hasher.finish(); + let seed_vec = hc64.to_le_bytes().into_iter().chain(0u8..8).collect::>(); + let seed: [u8; 16] = seed_vec.as_slice().try_into().unwrap(); + SeedableRng::from_seed(seed) +} + +pub struct TempDir(PathBuf); + +impl TempDir { + pub fn join(&self, path: &str) -> PathBuf { + let TempDir(ref p) = *self; + p.join(path) + } + + pub fn path(&self) -> &Path { + let TempDir(ref p) = *self; + p + } +} + +impl Drop for TempDir { + fn drop(&mut self) { + // Gee, seeing how we're testing the fs module I sure hope that we + // at least implement this correctly! + let TempDir(ref p) = *self; + let result = fs::remove_dir_all(p); + // Avoid panicking while panicking as this causes the process to + // immediately abort, without displaying test results. + if !thread::panicking() { + result.unwrap(); + } + } +} + +#[track_caller] // for `test_rng` +pub fn tmpdir() -> TempDir { + let p = env::temp_dir(); + let mut r = test_rng(); + let ret = p.join(&format!("rust-{}", r.next_u32())); + fs::create_dir(&ret).unwrap(); + TempDir(ret) +} diff --git a/library/std/tests/common/mod.rs b/library/std/tests/common/mod.rs index 7cf70c725e411..1e8e4cced6c03 100644 --- a/library/std/tests/common/mod.rs +++ b/library/std/tests/common/mod.rs @@ -18,7 +18,7 @@ pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { rand::SeedableRng::from_seed(seed) } -// Copied from std::sys_common::io +// Copied from std::test_helpers pub(crate) struct TempDir(PathBuf); impl TempDir { diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 75fac88252b10..620daec51149d 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -582,7 +582,7 @@ impl Step for Rustdoc { if !target_compiler.is_snapshot(builder) { panic!("rustdoc in stage 0 must be snapshot rustdoc"); } - return builder.initial_rustc.with_file_name(exe("rustdoc", target_compiler.host)); + return builder.initial_rustdoc.clone(); } let target = target_compiler.host; diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 2dd83d5938e9d..4e097b6750363 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -154,6 +154,7 @@ pub struct Build { targets: Vec, initial_rustc: PathBuf, + initial_rustdoc: PathBuf, initial_cargo: PathBuf, initial_lld: PathBuf, initial_relative_libdir: PathBuf, @@ -354,6 +355,7 @@ impl Build { initial_lld, initial_relative_libdir, initial_rustc: config.initial_rustc.clone(), + initial_rustdoc: config.initial_rustc.with_file_name(exe("rustdoc", config.build)), initial_cargo: config.initial_cargo.clone(), initial_sysroot: config.initial_sysroot.clone(), local_rebuild: config.local_rebuild, diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 92458bfaa3479..341d82599fd07 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -167,10 +167,10 @@ auto: <<: *job-linux-4c - name: dist-loongarch64-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - name: dist-loongarch64-musl - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - name: dist-ohos <<: *job-linux-4c diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 9fbeab5bf2e1f..5c5978b555985 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -144,8 +144,6 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::doc::DOC_NESTED_REFDEFS_INFO, crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, crate::doc::EMPTY_DOCS_INFO, - crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, - crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO, crate::doc::MISSING_SAFETY_DOC_INFO, @@ -162,6 +160,8 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, crate::empty_enum::EMPTY_ENUM_INFO, + crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, + crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, crate::empty_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO, crate::endian_bytes::BIG_ENDIAN_BYTES_INFO, diff --git a/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs b/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs deleted file mode 100644 index 6e85c6af642b5..0000000000000 --- a/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs +++ /dev/null @@ -1,345 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::{SpanRangeExt, snippet_indent}; -use clippy_utils::tokenize_with_text; -use itertools::Itertools; -use rustc_ast::AttrStyle; -use rustc_ast::token::CommentKind; -use rustc_errors::{Applicability, Diag, SuggestionStyle}; -use rustc_hir::{AttrKind, Attribute, ItemKind, Node}; -use rustc_lexer::TokenKind; -use rustc_lint::LateContext; -use rustc_span::{BytePos, ExpnKind, InnerSpan, Span, SpanData}; - -use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR}; - -#[derive(Debug, PartialEq, Clone, Copy)] -enum StopKind { - Attr, - Doc(CommentKind), -} - -impl StopKind { - fn is_doc(self) -> bool { - matches!(self, StopKind::Doc(_)) - } -} - -#[derive(Debug)] -struct Stop { - span: Span, - kind: StopKind, - first: usize, - last: usize, -} - -impl Stop { - fn convert_to_inner(&self) -> (Span, String) { - let inner = match self.kind { - // #|[...] - StopKind::Attr => InnerSpan::new(1, 1), - // /// or /** - // ^ ^ - StopKind::Doc(_) => InnerSpan::new(2, 3), - }; - (self.span.from_inner(inner), "!".into()) - } - - fn comment_out(&self, cx: &LateContext<'_>, suggestions: &mut Vec<(Span, String)>) { - match self.kind { - StopKind::Attr => { - if cx.tcx.sess.source_map().is_multiline(self.span) { - suggestions.extend([ - (self.span.shrink_to_lo(), "/* ".into()), - (self.span.shrink_to_hi(), " */".into()), - ]); - } else { - suggestions.push((self.span.shrink_to_lo(), "// ".into())); - } - }, - StopKind::Doc(CommentKind::Line) => suggestions.push((self.span.shrink_to_lo(), "// ".into())), - StopKind::Doc(CommentKind::Block) => { - // /** outer */ /*! inner */ - // ^ ^ - let asterisk = self.span.from_inner(InnerSpan::new(1, 2)); - suggestions.push((asterisk, String::new())); - }, - } - } - - fn from_attr(cx: &LateContext<'_>, attr: &Attribute) -> Option { - let SpanData { lo, hi, .. } = attr.span.data(); - let file = cx.tcx.sess.source_map().lookup_source_file(lo); - - Some(Self { - span: attr.span, - kind: match attr.kind { - AttrKind::Normal(_) => StopKind::Attr, - AttrKind::DocComment(comment_kind, _) => StopKind::Doc(comment_kind), - }, - first: file.lookup_line(file.relative_position(lo))?, - last: file.lookup_line(file.relative_position(hi))?, - }) - } -} - -/// Represents a set of attrs/doc comments separated by 1 or more empty lines -/// -/// ```ignore -/// /// chunk 1 docs -/// // not an empty line so also part of chunk 1 -/// #[chunk_1_attrs] // <-- prev_stop -/// -/// /* gap */ -/// -/// /// chunk 2 docs // <-- next_stop -/// #[chunk_2_attrs] -/// ``` -struct Gap<'a> { - /// The span of individual empty lines including the newline at the end of the line - empty_lines: Vec, - has_comment: bool, - next_stop: &'a Stop, - prev_stop: &'a Stop, - /// The chunk that includes [`prev_stop`](Self::prev_stop) - prev_chunk: &'a [Stop], -} - -impl<'a> Gap<'a> { - fn new(cx: &LateContext<'_>, prev_chunk: &'a [Stop], next_chunk: &'a [Stop]) -> Option { - let prev_stop = prev_chunk.last()?; - let next_stop = next_chunk.first()?; - let gap_span = prev_stop.span.between(next_stop.span); - let gap_snippet = gap_span.get_source_text(cx)?; - - let mut has_comment = false; - let mut empty_lines = Vec::new(); - - for (token, source, inner_span) in tokenize_with_text(&gap_snippet) { - match token { - TokenKind::BlockComment { - doc_style: None, - terminated: true, - } - | TokenKind::LineComment { doc_style: None } => has_comment = true, - TokenKind::Whitespace => { - let newlines = source.bytes().positions(|b| b == b'\n'); - empty_lines.extend( - newlines - .tuple_windows() - .map(|(a, b)| InnerSpan::new(inner_span.start + a + 1, inner_span.start + b)) - .map(|inner_span| gap_span.from_inner(inner_span)), - ); - }, - // Ignore cfg_attr'd out attributes as they may contain empty lines, could also be from macro - // shenanigans - _ => return None, - } - } - - (!empty_lines.is_empty()).then_some(Self { - empty_lines, - has_comment, - next_stop, - prev_stop, - prev_chunk, - }) - } - - fn contiguous_empty_lines(&self) -> impl Iterator + '_ { - self.empty_lines - // The `+ BytePos(1)` means "next line", because each empty line span is "N:1-N:1". - .chunk_by(|a, b| a.hi() + BytePos(1) == b.lo()) - .map(|chunk| { - let first = chunk.first().expect("at least one empty line"); - let last = chunk.last().expect("at least one empty line"); - // The BytePos subtraction here is safe, as before an empty line, there must be at least one - // attribute/comment. The span needs to start at the end of the previous line. - first.with_lo(first.lo() - BytePos(1)).with_hi(last.hi()) - }) - } -} - -/// If the node the attributes/docs apply to is the first in the module/crate suggest converting -/// them to inner attributes/docs -fn suggest_inner(cx: &LateContext<'_>, diag: &mut Diag<'_, ()>, kind: StopKind, gaps: &[Gap<'_>]) { - let Some(owner) = cx.last_node_with_lint_attrs.as_owner() else { - return; - }; - let parent_desc = match cx.tcx.parent_hir_node(owner.into()) { - Node::Item(item) - if let ItemKind::Mod(parent_mod) = item.kind - && let [first, ..] = parent_mod.item_ids - && first.owner_id == owner => - { - "parent module" - }, - Node::Crate(crate_mod) - if let Some(first) = crate_mod - .item_ids - .iter() - .map(|&id| cx.tcx.hir().item(id)) - // skip prelude imports - .find(|item| !matches!(item.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) - && first.owner_id == owner => - { - "crate" - }, - _ => return, - }; - - diag.multipart_suggestion_verbose( - match kind { - StopKind::Attr => format!("if the attribute should apply to the {parent_desc} use an inner attribute"), - StopKind::Doc(_) => format!("if the comment should document the {parent_desc} use an inner doc comment"), - }, - gaps.iter() - .flat_map(|gap| gap.prev_chunk) - .map(Stop::convert_to_inner) - .collect(), - Applicability::MaybeIncorrect, - ); -} - -fn check_gaps(cx: &LateContext<'_>, gaps: &[Gap<'_>]) -> bool { - let Some(first_gap) = gaps.first() else { - return false; - }; - let empty_lines = || gaps.iter().flat_map(|gap| gap.empty_lines.iter().copied()); - let contiguous_empty_lines = || gaps.iter().flat_map(Gap::contiguous_empty_lines); - let mut has_comment = false; - let mut has_attr = false; - for gap in gaps { - has_comment |= gap.has_comment; - if !has_attr { - has_attr = gap.prev_chunk.iter().any(|stop| stop.kind == StopKind::Attr); - } - } - let kind = first_gap.prev_stop.kind; - let (lint, kind_desc) = match kind { - StopKind::Attr => (EMPTY_LINE_AFTER_OUTER_ATTR, "outer attribute"), - StopKind::Doc(_) => (EMPTY_LINE_AFTER_DOC_COMMENTS, "doc comment"), - }; - let (lines, are, them) = if empty_lines().nth(1).is_some() { - ("lines", "are", "them") - } else { - ("line", "is", "it") - }; - span_lint_and_then( - cx, - lint, - first_gap.prev_stop.span.to(empty_lines().last().unwrap()), - format!("empty {lines} after {kind_desc}"), - |diag| { - if let Some(owner) = cx.last_node_with_lint_attrs.as_owner() { - let def_id = owner.to_def_id(); - let def_descr = cx.tcx.def_descr(def_id); - diag.span_label( - cx.tcx.def_span(def_id), - match kind { - StopKind::Attr => format!("the attribute applies to this {def_descr}"), - StopKind::Doc(_) => format!("the comment documents this {def_descr}"), - }, - ); - } - - diag.multipart_suggestion_with_style( - format!("if the empty {lines} {are} unintentional remove {them}"), - contiguous_empty_lines() - .map(|empty_lines| (empty_lines, String::new())) - .collect(), - Applicability::MaybeIncorrect, - SuggestionStyle::HideCodeAlways, - ); - - if has_comment && kind.is_doc() { - // Likely doc comments that applied to some now commented out code - // - // /// Old docs for Foo - // // struct Foo; - - let mut suggestions = Vec::new(); - for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) { - stop.comment_out(cx, &mut suggestions); - } - let name = match cx.tcx.hir().opt_name(cx.last_node_with_lint_attrs) { - Some(name) => format!("`{name}`"), - None => "this".into(), - }; - diag.multipart_suggestion_verbose( - format!("if the doc comment should not document {name} comment it out"), - suggestions, - Applicability::MaybeIncorrect, - ); - } else { - suggest_inner(cx, diag, kind, gaps); - } - - if kind == StopKind::Doc(CommentKind::Line) - && gaps - .iter() - .all(|gap| !gap.has_comment && gap.next_stop.kind == StopKind::Doc(CommentKind::Line)) - { - // Commentless empty gaps between line doc comments, possibly intended to be part of the markdown - - let indent = snippet_indent(cx, first_gap.prev_stop.span).unwrap_or_default(); - diag.multipart_suggestion_verbose( - format!("if the documentation should include the empty {lines} include {them} in the comment"), - empty_lines() - .map(|empty_line| (empty_line, format!("{indent}///"))) - .collect(), - Applicability::MaybeIncorrect, - ); - } - }, - ); - kind.is_doc() -} - -/// Returns `true` if [`EMPTY_LINE_AFTER_DOC_COMMENTS`] triggered, used to skip other doc comment -/// lints where they would be confusing -/// -/// [`EMPTY_LINE_AFTER_OUTER_ATTR`] is also here to share an implementation but does not return -/// `true` if it triggers -pub(super) fn check(cx: &LateContext<'_>, attrs: &[Attribute]) -> bool { - let mut outer = attrs - .iter() - .filter(|attr| attr.style == AttrStyle::Outer && !attr.span.from_expansion()) - .map(|attr| Stop::from_attr(cx, attr)) - .collect::>>() - .unwrap_or_default(); - - if outer.is_empty() { - return false; - } - - // Push a fake attribute Stop for the item itself so we check for gaps between the last outer - // attr/doc comment and the item they apply to - let span = cx.tcx.hir().span(cx.last_node_with_lint_attrs); - if !span.from_expansion() - && let Ok(line) = cx.tcx.sess.source_map().lookup_line(span.lo()) - { - outer.push(Stop { - span, - kind: StopKind::Attr, - first: line.line, - // last doesn't need to be accurate here, we don't compare it with anything - last: line.line, - }); - } - - let mut gaps = Vec::new(); - let mut last = 0; - for pos in outer - .array_windows() - .positions(|[a, b]| b.first.saturating_sub(a.last) > 1) - { - // we want to be after the first stop in the window - let pos = pos + 1; - if let Some(gap) = Gap::new(cx, &outer[last..pos], &outer[pos..]) { - last = pos; - gaps.push(gap); - } - } - - check_gaps(cx, &gaps) -} diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index 3d8ce7becdbda..42e1f7fd950d4 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -33,7 +33,6 @@ use rustc_span::{Span, sym}; use std::ops::Range; use url::Url; -mod empty_line_after; mod include_in_doc_without_cfg; mod link_with_quotes; mod markdown; @@ -491,82 +490,6 @@ declare_clippy_lint! { "ensure the first documentation paragraph is short" } -declare_clippy_lint! { - /// ### What it does - /// Checks for empty lines after outer attributes - /// - /// ### Why is this bad? - /// The attribute may have meant to be an inner attribute (`#![attr]`). If - /// it was meant to be an outer attribute (`#[attr]`) then the empty line - /// should be removed - /// - /// ### Example - /// ```no_run - /// #[allow(dead_code)] - /// - /// fn not_quite_good_code() {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// // Good (as inner attribute) - /// #![allow(dead_code)] - /// - /// fn this_is_fine() {} - /// - /// // or - /// - /// // Good (as outer attribute) - /// #[allow(dead_code)] - /// fn this_is_fine_too() {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EMPTY_LINE_AFTER_OUTER_ATTR, - suspicious, - "empty line after outer attribute" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for empty lines after doc comments. - /// - /// ### Why is this bad? - /// The doc comment may have meant to be an inner doc comment, regular - /// comment or applied to some old code that is now commented out. If it was - /// intended to be a doc comment, then the empty line should be removed. - /// - /// ### Example - /// ```no_run - /// /// Some doc comment with a blank line after it. - /// - /// fn f() {} - /// - /// /// Docs for `old_code` - /// // fn old_code() {} - /// - /// fn new_code() {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// //! Convert it to an inner doc comment - /// - /// // Or a regular comment - /// - /// /// Or remove the empty line - /// fn f() {} - /// - /// // /// Docs for `old_code` - /// // fn old_code() {} - /// - /// fn new_code() {} - /// ``` - #[clippy::version = "1.70.0"] - pub EMPTY_LINE_AFTER_DOC_COMMENTS, - suspicious, - "empty line after doc comments" -} - declare_clippy_lint! { /// ### What it does /// Checks if included files in doc comments are included only for `cfg(doc)`. @@ -650,8 +573,6 @@ impl_lint_pass!(Documentation => [ EMPTY_DOCS, DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS, - EMPTY_LINE_AFTER_OUTER_ATTR, - EMPTY_LINE_AFTER_DOC_COMMENTS, TOO_LONG_FIRST_DOC_PARAGRAPH, DOC_INCLUDE_WITHOUT_CFG, ]); @@ -784,7 +705,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ } include_in_doc_without_cfg::check(cx, attrs); - if suspicious_doc_comments::check(cx, attrs) || empty_line_after::check(cx, attrs) || is_doc_hidden(attrs) { + if suspicious_doc_comments::check(cx, attrs) || is_doc_hidden(attrs) { return None; } diff --git a/src/tools/clippy/clippy_lints/src/empty_line_after.rs b/src/tools/clippy/clippy_lints/src/empty_line_after.rs new file mode 100644 index 0000000000000..578ff6e38a6a6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/empty_line_after.rs @@ -0,0 +1,492 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{SpanRangeExt, snippet_indent}; +use clippy_utils::tokenize_with_text; +use itertools::Itertools; +use rustc_ast::token::CommentKind; +use rustc_ast::{AssocItemKind, AttrKind, AttrStyle, Attribute, Crate, Item, ItemKind, ModKind, NodeId}; +use rustc_errors::{Applicability, Diag, SuggestionStyle}; +use rustc_lexer::TokenKind; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_session::impl_lint_pass; +use rustc_span::symbol::kw; +use rustc_span::{BytePos, ExpnKind, Ident, InnerSpan, Span, SpanData, Symbol}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for empty lines after outer attributes + /// + /// ### Why is this bad? + /// The attribute may have meant to be an inner attribute (`#![attr]`). If + /// it was meant to be an outer attribute (`#[attr]`) then the empty line + /// should be removed + /// + /// ### Example + /// ```no_run + /// #[allow(dead_code)] + /// + /// fn not_quite_good_code() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// // Good (as inner attribute) + /// #![allow(dead_code)] + /// + /// fn this_is_fine() {} + /// + /// // or + /// + /// // Good (as outer attribute) + /// #[allow(dead_code)] + /// fn this_is_fine_too() {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EMPTY_LINE_AFTER_OUTER_ATTR, + suspicious, + "empty line after outer attribute" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for empty lines after doc comments. + /// + /// ### Why is this bad? + /// The doc comment may have meant to be an inner doc comment, regular + /// comment or applied to some old code that is now commented out. If it was + /// intended to be a doc comment, then the empty line should be removed. + /// + /// ### Example + /// ```no_run + /// /// Some doc comment with a blank line after it. + /// + /// fn f() {} + /// + /// /// Docs for `old_code` + /// // fn old_code() {} + /// + /// fn new_code() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// //! Convert it to an inner doc comment + /// + /// // Or a regular comment + /// + /// /// Or remove the empty line + /// fn f() {} + /// + /// // /// Docs for `old_code` + /// // fn old_code() {} + /// + /// fn new_code() {} + /// ``` + #[clippy::version = "1.70.0"] + pub EMPTY_LINE_AFTER_DOC_COMMENTS, + suspicious, + "empty line after doc comments" +} + +#[derive(Debug)] +struct ItemInfo { + kind: &'static str, + name: Symbol, + span: Span, + mod_items: Option, +} + +pub struct EmptyLineAfter { + items: Vec, +} + +impl_lint_pass!(EmptyLineAfter => [ + EMPTY_LINE_AFTER_OUTER_ATTR, + EMPTY_LINE_AFTER_DOC_COMMENTS, +]); + +impl EmptyLineAfter { + pub fn new() -> Self { + Self { items: Vec::new() } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +enum StopKind { + Attr, + Doc(CommentKind), +} + +impl StopKind { + fn is_doc(self) -> bool { + matches!(self, StopKind::Doc(_)) + } +} + +#[derive(Debug)] +struct Stop { + span: Span, + kind: StopKind, + first: usize, + last: usize, +} + +impl Stop { + fn convert_to_inner(&self) -> (Span, String) { + let inner = match self.kind { + // #![...] + StopKind::Attr => InnerSpan::new(1, 1), + // /// or /** + // ^ ^ + StopKind::Doc(_) => InnerSpan::new(2, 3), + }; + (self.span.from_inner(inner), "!".into()) + } + + fn comment_out(&self, cx: &EarlyContext<'_>, suggestions: &mut Vec<(Span, String)>) { + match self.kind { + StopKind::Attr => { + if cx.sess().source_map().is_multiline(self.span) { + suggestions.extend([ + (self.span.shrink_to_lo(), "/* ".into()), + (self.span.shrink_to_hi(), " */".into()), + ]); + } else { + suggestions.push((self.span.shrink_to_lo(), "// ".into())); + } + }, + StopKind::Doc(CommentKind::Line) => suggestions.push((self.span.shrink_to_lo(), "// ".into())), + StopKind::Doc(CommentKind::Block) => { + // /** outer */ /*! inner */ + // ^ ^ + let asterisk = self.span.from_inner(InnerSpan::new(1, 2)); + suggestions.push((asterisk, String::new())); + }, + } + } + + fn from_attr(cx: &EarlyContext<'_>, attr: &Attribute) -> Option { + let SpanData { lo, hi, .. } = attr.span.data(); + let file = cx.sess().source_map().lookup_source_file(lo); + + Some(Self { + span: attr.span, + kind: match attr.kind { + AttrKind::Normal(_) => StopKind::Attr, + AttrKind::DocComment(comment_kind, _) => StopKind::Doc(comment_kind), + }, + first: file.lookup_line(file.relative_position(lo))?, + last: file.lookup_line(file.relative_position(hi))?, + }) + } +} + +/// Represents a set of attrs/doc comments separated by 1 or more empty lines +/// +/// ```ignore +/// /// chunk 1 docs +/// // not an empty line so also part of chunk 1 +/// #[chunk_1_attrs] // <-- prev_stop +/// +/// /* gap */ +/// +/// /// chunk 2 docs // <-- next_stop +/// #[chunk_2_attrs] +/// ``` +struct Gap<'a> { + /// The span of individual empty lines including the newline at the end of the line + empty_lines: Vec, + has_comment: bool, + next_stop: &'a Stop, + prev_stop: &'a Stop, + /// The chunk that includes [`prev_stop`](Self::prev_stop) + prev_chunk: &'a [Stop], +} + +impl<'a> Gap<'a> { + fn new(cx: &EarlyContext<'_>, prev_chunk: &'a [Stop], next_chunk: &'a [Stop]) -> Option { + let prev_stop = prev_chunk.last()?; + let next_stop = next_chunk.first()?; + let gap_span = prev_stop.span.between(next_stop.span); + let gap_snippet = gap_span.get_source_text(cx)?; + + let mut has_comment = false; + let mut empty_lines = Vec::new(); + + for (token, source, inner_span) in tokenize_with_text(&gap_snippet) { + match token { + TokenKind::BlockComment { + doc_style: None, + terminated: true, + } + | TokenKind::LineComment { doc_style: None } => has_comment = true, + TokenKind::Whitespace => { + let newlines = source.bytes().positions(|b| b == b'\n'); + empty_lines.extend( + newlines + .tuple_windows() + .map(|(a, b)| InnerSpan::new(inner_span.start + a + 1, inner_span.start + b)) + .map(|inner_span| gap_span.from_inner(inner_span)), + ); + }, + // Ignore cfg_attr'd out attributes as they may contain empty lines, could also be from macro + // shenanigans + _ => return None, + } + } + + (!empty_lines.is_empty()).then_some(Self { + empty_lines, + has_comment, + next_stop, + prev_stop, + prev_chunk, + }) + } + + fn contiguous_empty_lines(&self) -> impl Iterator + '_ { + self.empty_lines + // The `+ BytePos(1)` means "next line", because each empty line span is "N:1-N:1". + .chunk_by(|a, b| a.hi() + BytePos(1) == b.lo()) + .map(|chunk| { + let first = chunk.first().expect("at least one empty line"); + let last = chunk.last().expect("at least one empty line"); + // The BytePos subtraction here is safe, as before an empty line, there must be at least one + // attribute/comment. The span needs to start at the end of the previous line. + first.with_lo(first.lo() - BytePos(1)).with_hi(last.hi()) + }) + } +} + +impl EmptyLineAfter { + fn check_gaps(&self, cx: &EarlyContext<'_>, gaps: &[Gap<'_>], id: NodeId) { + let Some(first_gap) = gaps.first() else { + return; + }; + let empty_lines = || gaps.iter().flat_map(|gap| gap.empty_lines.iter().copied()); + let contiguous_empty_lines = || gaps.iter().flat_map(Gap::contiguous_empty_lines); + let mut has_comment = false; + let mut has_attr = false; + for gap in gaps { + has_comment |= gap.has_comment; + if !has_attr { + has_attr = gap.prev_chunk.iter().any(|stop| stop.kind == StopKind::Attr); + } + } + let kind = first_gap.prev_stop.kind; + let (lint, kind_desc) = match kind { + StopKind::Attr => (EMPTY_LINE_AFTER_OUTER_ATTR, "outer attribute"), + StopKind::Doc(_) => (EMPTY_LINE_AFTER_DOC_COMMENTS, "doc comment"), + }; + let (lines, are, them) = if empty_lines().nth(1).is_some() { + ("lines", "are", "them") + } else { + ("line", "is", "it") + }; + span_lint_and_then( + cx, + lint, + first_gap.prev_stop.span.to(empty_lines().last().unwrap()), + format!("empty {lines} after {kind_desc}"), + |diag| { + let info = self.items.last().unwrap(); + diag.span_label(info.span, match kind { + StopKind::Attr => format!("the attribute applies to this {}", info.kind), + StopKind::Doc(_) => format!("the comment documents this {}", info.kind), + }); + + diag.multipart_suggestion_with_style( + format!("if the empty {lines} {are} unintentional, remove {them}"), + contiguous_empty_lines() + .map(|empty_lines| (empty_lines, String::new())) + .collect(), + Applicability::MaybeIncorrect, + SuggestionStyle::HideCodeAlways, + ); + + if has_comment && kind.is_doc() { + // Likely doc comments that applied to some now commented out code + // + // /// Old docs for Foo + // // struct Foo; + + let mut suggestions = Vec::new(); + for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) { + stop.comment_out(cx, &mut suggestions); + } + diag.multipart_suggestion_verbose( + format!("if the doc comment should not document `{}` comment it out", info.name), + suggestions, + Applicability::MaybeIncorrect, + ); + } else { + self.suggest_inner(diag, kind, gaps, id); + } + + if kind == StopKind::Doc(CommentKind::Line) + && gaps + .iter() + .all(|gap| !gap.has_comment && gap.next_stop.kind == StopKind::Doc(CommentKind::Line)) + { + // Commentless empty gaps between line doc comments, possibly intended to be part of the markdown + + let indent = snippet_indent(cx, first_gap.prev_stop.span).unwrap_or_default(); + diag.multipart_suggestion_verbose( + format!("if the documentation should include the empty {lines} include {them} in the comment"), + empty_lines() + .map(|empty_line| (empty_line, format!("{indent}///"))) + .collect(), + Applicability::MaybeIncorrect, + ); + } + }, + ); + } + + /// If the node the attributes/docs apply to is the first in the module/crate suggest converting + /// them to inner attributes/docs + fn suggest_inner(&self, diag: &mut Diag<'_, ()>, kind: StopKind, gaps: &[Gap<'_>], id: NodeId) { + if let Some(parent) = self.items.iter().rev().nth(1) + && (parent.kind == "module" || parent.kind == "crate") + && parent.mod_items == Some(id) + { + let desc = if parent.kind == "module" { + "parent module" + } else { + parent.kind + }; + diag.multipart_suggestion_verbose( + match kind { + StopKind::Attr => format!("if the attribute should apply to the {desc} use an inner attribute"), + StopKind::Doc(_) => format!("if the comment should document the {desc} use an inner doc comment"), + }, + gaps.iter() + .flat_map(|gap| gap.prev_chunk) + .map(Stop::convert_to_inner) + .collect(), + Applicability::MaybeIncorrect, + ); + } + } + + fn check_item_kind( + &mut self, + cx: &EarlyContext<'_>, + kind: &ItemKind, + ident: &Ident, + span: Span, + attrs: &[Attribute], + id: NodeId, + ) { + self.items.push(ItemInfo { + kind: kind.descr(), + name: ident.name, + span: if span.contains(ident.span) { + span.with_hi(ident.span.hi()) + } else { + span.with_hi(span.lo()) + }, + mod_items: match kind { + ItemKind::Mod(_, ModKind::Loaded(items, _, _, _)) => items + .iter() + .filter(|i| !matches!(i.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) + .map(|i| i.id) + .next(), + _ => None, + }, + }); + + let mut outer = attrs + .iter() + .filter(|attr| attr.style == AttrStyle::Outer && !attr.span.from_expansion()) + .map(|attr| Stop::from_attr(cx, attr)) + .collect::>>() + .unwrap_or_default(); + + if outer.is_empty() { + return; + } + + // Push a fake attribute Stop for the item itself so we check for gaps between the last outer + // attr/doc comment and the item they apply to + let span = self.items.last().unwrap().span; + if !span.from_expansion() + && let Ok(line) = cx.sess().source_map().lookup_line(span.lo()) + { + outer.push(Stop { + span, + kind: StopKind::Attr, + first: line.line, + // last doesn't need to be accurate here, we don't compare it with anything + last: line.line, + }); + } + + let mut gaps = Vec::new(); + let mut last = 0; + for pos in outer + .array_windows() + .positions(|[a, b]| b.first.saturating_sub(a.last) > 1) + { + // we want to be after the first stop in the window + let pos = pos + 1; + if let Some(gap) = Gap::new(cx, &outer[last..pos], &outer[pos..]) { + last = pos; + gaps.push(gap); + } + } + + self.check_gaps(cx, &gaps, id); + } +} + +impl EarlyLintPass for EmptyLineAfter { + fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) { + self.items.push(ItemInfo { + kind: "crate", + name: kw::Crate, + span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()), + mod_items: krate + .items + .iter() + .filter(|i| !matches!(i.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) + .map(|i| i.id) + .next(), + }); + } + + fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.items.pop(); + } + fn check_impl_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.items.pop(); + } + fn check_trait_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.items.pop(); + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + self.check_item_kind( + cx, + &item.kind.clone().into(), + &item.ident, + item.span, + &item.attrs, + item.id, + ); + } + + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + self.check_item_kind( + cx, + &item.kind.clone().into(), + &item.ident, + item.span, + &item.attrs, + item.id, + ); + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + self.check_item_kind(cx, &item.kind, &item.ident, item.span, &item.attrs, item.id); + } +} diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 8887ab7ec0d7b..13218331a67b2 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -126,6 +126,7 @@ mod duplicate_mod; mod else_if_without_else; mod empty_drop; mod empty_enum; +mod empty_line_after; mod empty_with_brackets; mod endian_bytes; mod entry; @@ -973,6 +974,7 @@ 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_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new())); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); store.register_late_pass(|_| Box::::default()); diff --git a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs index 04446787b6c29..a065654e319d0 100644 --- a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs +++ b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs @@ -66,3 +66,19 @@ fn escape_3() {} /// Backslashes ` \` within code blocks don't count. fn escape_4() {} + +trait Foo { + fn bar(); +} + +struct Bar; +impl Foo for Bar { + // NOTE: false positive + /// Returns an `Option` from a i64, assuming a 1-index, January = 1. + /// + /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + /// ---------------------------| -------------------- | --------------------- | ... | ----- + /// ``: | Some(Month::January) | Some(Month::February) | ... | + /// Some(Month::December) + fn bar() {} +} diff --git a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr index 50324010e97f7..c9fd25eb1a1c8 100644 --- a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr +++ b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr @@ -94,5 +94,17 @@ LL | /// Escaped \` ` backticks don't count, but unescaped backticks do. | = help: a backtick may be missing a pair -error: aborting due to 10 previous errors +error: backticks are unbalanced + --> tests/ui/doc/unbalanced_ticks.rs:79:9 + | +LL | /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + | _________^ +LL | | /// ---------------------------| -------------------- | --------------------- | ... | ----- +LL | | /// ``: | Some(Month::January) | Some(Month::February) | ... | +LL | | /// Some(Month::December) + | |_____________________________^ + | + = help: a backtick may be missing a pair + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed index fd6a94b6a80c8..3772b465fdb2a 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed @@ -132,4 +132,13 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed index 7a57dcd92332b..3028d03b66994 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed @@ -141,4 +141,13 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs index 1da761a5c3d52..ae4ebc271fa1c 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs @@ -144,4 +144,14 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr index c5d5f3d375947..7b197ae67e00a 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr @@ -5,11 +5,11 @@ LL | / /// for the crate LL | | | |_^ LL | fn first_in_crate() {} - | ------------------- the comment documents this function + | ----------------- the comment documents this function | = note: `-D clippy::empty-line-after-doc-comments` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::empty_line_after_doc_comments)]` - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the crate use an inner doc comment | LL ~ //! Meant to be an @@ -24,9 +24,9 @@ LL | / /// for the module LL | | | |_^ LL | fn first_in_module() {} - | -------------------- the comment documents this function + | ------------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the parent module use an inner doc comment | LL ~ //! Meant to be an @@ -42,9 +42,9 @@ LL | | | |_^ LL | /// Blank line LL | fn indented() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the documentation should include the empty line include it in the comment | LL | /// @@ -57,9 +57,9 @@ LL | / /// This should produce a warning LL | | | |_^ LL | fn with_doc_and_newline() {} - | ------------------------- the comment documents this function + | ----------------------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after doc comment --> tests/ui/empty_line_after/doc_comments.rs:44:1 @@ -72,9 +72,9 @@ LL | | | |_^ ... LL | fn three_attributes() {} - | --------------------- the comment documents this function + | ------------------- the comment documents this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them error: empty line after doc comment --> tests/ui/empty_line_after/doc_comments.rs:56:5 @@ -84,9 +84,9 @@ LL | | // fn old_code() {} LL | | | |_^ LL | fn new_code() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code` comment it out | LL | // /// docs for `old_code` @@ -106,7 +106,7 @@ LL | | LL | struct Multiple; | --------------- the comment documents this struct | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them help: if the doc comment should not document `Multiple` comment it out | LL ~ // /// Docs @@ -126,9 +126,9 @@ LL | | */ LL | | | |_^ LL | fn first_in_module() {} - | -------------------- the comment documents this function + | ------------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the parent module use an inner doc comment | LL | /*! @@ -145,9 +145,9 @@ LL | | | |_^ ... LL | fn new_code() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code` comment it out | LL - /** @@ -163,13 +163,24 @@ LL | | | |_^ LL | /// Docs for `new_code2` LL | fn new_code2() {} - | -------------- the comment documents this function + | ------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code2` comment it out | LL | // /// Docs for `old_code2` | ++ -error: aborting due to 10 previous errors +error: empty line after doc comment + --> tests/ui/empty_line_after/doc_comments.rs:152:5 + | +LL | / /// comment on assoc item +LL | | + | |_^ +LL | fn bar() {} + | ------ the comment documents this function + | + = help: if the empty line is unintentional, remove it + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr b/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr index a95306e2fa335..519ba6e67615c 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr +++ b/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr @@ -5,11 +5,11 @@ LL | / #[crate_type = "lib"] LL | | | |_^ LL | fn first_in_crate() {} - | ------------------- the attribute applies to this function + | ----------------- the attribute applies to this function | = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::empty_line_after_outer_attr)]` - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the attribute should apply to the crate use an inner attribute | LL | #![crate_type = "lib"] @@ -23,9 +23,9 @@ LL | | | |_^ LL | /// some comment LL | fn with_one_newline_and_comment() {} - | --------------------------------- the attribute applies to this function + | ------------------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:23:1 @@ -34,9 +34,9 @@ LL | / #[inline] LL | | | |_^ LL | fn with_one_newline() {} - | --------------------- the attribute applies to this function + | ------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:30:5 @@ -46,9 +46,9 @@ LL | | LL | | | |_^ LL | fn with_two_newlines() {} - | ---------------------- the attribute applies to this function + | -------------------- the attribute applies to this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them help: if the attribute should apply to the parent module use an inner attribute | LL | #![crate_type = "lib"] @@ -63,7 +63,7 @@ LL | | LL | enum Baz { | -------- the attribute applies to this enum | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:45:1 @@ -74,7 +74,7 @@ LL | | LL | struct Foo { | ---------- the attribute applies to this struct | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:53:1 @@ -85,7 +85,7 @@ LL | | LL | mod foo {} | ------- the attribute applies to this module | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:58:1 @@ -95,9 +95,9 @@ LL | | // Still lint cases where the empty line does not immediately follow the LL | | | |_^ LL | fn comment_before_empty_line() {} - | ------------------------------ the attribute applies to this function + | ---------------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:64:1 @@ -107,9 +107,9 @@ LL | / #[allow(unused)] LL | | | |_^ LL | pub fn isolated_comment() {} - | ------------------------- the attribute applies to this function + | ----------------------- the attribute applies to this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed b/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed index 614fc03571e53..d3df6a41cb12a 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed @@ -1,5 +1,6 @@ #![allow(unused)] #![warn(clippy::suspicious_doc_comments)] +#![allow(clippy::empty_line_after_doc_comments)] //! Real module documentation. //! Fake module documentation. diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.rs b/src/tools/clippy/tests/ui/suspicious_doc_comments.rs index 7dcba0fefc981..04db2b199c097 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.rs +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.rs @@ -1,5 +1,6 @@ #![allow(unused)] #![warn(clippy::suspicious_doc_comments)] +#![allow(clippy::empty_line_after_doc_comments)] //! Real module documentation. ///! Fake module documentation. diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr b/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr index f12053b1595a1..c34e39cd0fcb5 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr @@ -1,5 +1,5 @@ error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:5:1 + --> tests/ui/suspicious_doc_comments.rs:6:1 | LL | ///! Fake module documentation. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | //! Fake module documentation. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:9:5 + --> tests/ui/suspicious_doc_comments.rs:10:5 | LL | ///! This module contains useful functions. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | //! This module contains useful functions. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:21:5 + --> tests/ui/suspicious_doc_comments.rs:22:5 | LL | / /**! This module contains useful functions. LL | | */ @@ -36,7 +36,7 @@ LL + */ | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:35:5 + --> tests/ui/suspicious_doc_comments.rs:36:5 | LL | / ///! This module LL | | ///! contains @@ -51,7 +51,7 @@ LL ~ //! useful functions. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:43:5 + --> tests/ui/suspicious_doc_comments.rs:44:5 | LL | / ///! a LL | | ///! b @@ -64,7 +64,7 @@ LL ~ //! b | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:51:5 + --> tests/ui/suspicious_doc_comments.rs:52:5 | LL | ///! a | ^^^^^^ @@ -75,7 +75,7 @@ LL | //! a | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:57:5 + --> tests/ui/suspicious_doc_comments.rs:58:5 | LL | / ///! a LL | | @@ -90,7 +90,7 @@ LL ~ //! b | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:69:5 + --> tests/ui/suspicious_doc_comments.rs:70:5 | LL | ///! Very cool macro | ^^^^^^^^^^^^^^^^^^^^ @@ -101,7 +101,7 @@ LL | //! Very cool macro | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:76:5 + --> tests/ui/suspicious_doc_comments.rs:77:5 | LL | ///! Huh. | ^^^^^^^^^ diff --git a/tests/codegen/terminating-catchpad.rs b/tests/codegen/terminating-catchpad.rs new file mode 100644 index 0000000000000..17d8879630094 --- /dev/null +++ b/tests/codegen/terminating-catchpad.rs @@ -0,0 +1,37 @@ +//@ revisions: emscripten wasi seh +//@[emscripten] compile-flags: --target wasm32-unknown-emscripten -Z emscripten-wasm-eh +//@[wasi] compile-flags: --target wasm32-wasip1 -C panic=unwind +//@[seh] compile-flags: --target x86_64-pc-windows-msvc +//@[emscripten] needs-llvm-components: webassembly +//@[wasi] needs-llvm-components: webassembly +//@[seh] needs-llvm-components: x86 + +// Ensure a catch-all generates: +// - `catchpad ... [ptr null]` on Wasm (otherwise LLVM gets confused) +// - `catchpad ... [ptr null, i32 64, ptr null]` on Windows (otherwise we catch SEH exceptions) + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "lib"] +#![no_std] +#![no_core] + +#[lang = "sized"] +trait Sized {} + +unsafe extern "C-unwind" { + safe fn unwinds(); +} + +#[lang = "panic_cannot_unwind"] +fn panic_cannot_unwind() -> ! { + loop {} +} + +#[no_mangle] +#[rustc_nounwind] +pub fn doesnt_unwind() { + // emscripten: %catchpad = catchpad within %catchswitch [ptr null] + // wasi: %catchpad = catchpad within %catchswitch [ptr null] + // seh: %catchpad = catchpad within %catchswitch [ptr null, i32 64, ptr null] + unwinds(); +}