diff --git a/compiler/rustc_codegen_ssa/src/mir/retag.rs b/compiler/rustc_codegen_ssa/src/mir/retag.rs index 6a3316fa93992..483df05e4d14f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/retag.rs +++ b/compiler/rustc_codegen_ssa/src/mir/retag.rs @@ -151,7 +151,7 @@ impl<'a, 'tcx, V> RetagPlan { let boxed_ty = layout.ty.expect_boxed_ty(); let boxed_layout = bx.layout_of(boxed_ty); if let Some(mut plan) = Self::emit_retag(bx, boxed_layout, None, is_fn_entry) { - // `Unique` + // `BoxRaw` let unique = layout.field(bx, 0); assert_eq!(unique.fields.count(), 2); plan = plan.for_field(FieldIdx::ZERO); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 098990e8da2bb..b1b8ecb7d1844 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -426,35 +426,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Ty::new_error(tcx, guar); } - let oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); + // Resolve what we can for `Not` and `Neg`, but allow later constraints + // to determine an unresolved operand type, + // so we call `deeply_resolve_ignoring_regions_with_obligations` for them. match unop { - hir::UnOp::Deref => self.lookup_derefing(expr, oprnd, oprnd_t).unwrap_or_else(|| { - let mut err = - self.dcx().create_err(CantDereference { span: expr.span, ty: oprnd_t }); - let sp = tcx.sess.source_map().start_point(expr.span).with_parent(None); - if let Some(sp) = - tcx.sess.psess.complete_stmt_exprs_before_bin_op_lookalike.borrow().get(&sp) - { - err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); - } - // The operand may be an uncalled function, in which case it is its return type - // the user meant to dereference. Only suggest the call when that return type is - // itself dereferenceable, mirroring the checks `lookup_derefing` just failed. - self.suggest_fn_call(&mut err, oprnd, oprnd_t, |output| { - output.builtin_deref(true).is_some() - || self.tcx.lang_items().deref_trait().is_some_and(|deref_trait| { - self.type_implements_trait(deref_trait, [output], self.param_env) - .may_apply() - }) - }); - Ty::new_error(tcx, err.emit_err()) - }), + hir::UnOp::Deref => { + // Dereferencing must distinguish builtin pointers from + // overloaded `Deref`, so it still requires a structurally resolved type. + // + // This is necessary as raw pointers do not implement `Deref`. + let oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); + self.lookup_derefing(expr, oprnd, oprnd_t).unwrap_or_else(|| { + let mut err = + self.dcx().create_err(CantDereference { span: expr.span, ty: oprnd_t }); + let sp = tcx.sess.source_map().start_point(expr.span).with_parent(None); + if let Some(sp) = + tcx.sess.psess.complete_stmt_exprs_before_bin_op_lookalike.borrow().get(&sp) + { + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + } + // The operand may be an uncalled function, in which case it is its return type + // the user meant to dereference. Only suggest the call when that return type is + // itself dereferenceable, mirroring the checks `lookup_derefing` just failed. + self.suggest_fn_call(&mut err, oprnd, oprnd_t, |output| { + output.builtin_deref(true).is_some() + || self.tcx.lang_items().deref_trait().is_some_and(|deref_trait| { + self.type_implements_trait(deref_trait, [output], self.param_env) + .may_apply() + }) + }); + Ty::new_error(tcx, err.emit_err()) + }) + } hir::UnOp::Not => { + let oprnd_t = self.deeply_resolve_ignoring_regions_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool { oprnd_t } else { result } } hir::UnOp::Neg => { + let oprnd_t = self.deeply_resolve_ignoring_regions_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_numeric() { oprnd_t } else { result } diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index 109beed7de947..89e20e03afe83 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -205,16 +205,13 @@ pub struct BTreeMap< #[stable(feature = "btree_drop", since = "1.7.0")] unsafe impl<#[may_dangle] K, #[may_dangle] V, A: AllocatorClone> Drop for BTreeMap { fn drop(&mut self) { - // Skip `into_iter` for an empty map: `dying_next` is too costly to inline, so the - // empty drop isn't optimised away (see #161375). - if self.root.is_some() { - // SAFETY: `self` is not used after this and none of its fields are dropped again: - // `alloc` is `ManuallyDrop` and `root` has no drop glue. - drop(unsafe { ptr::read(self) }.into_iter()) + if let Some(root) = self.root.take() { + // SAFETY: The tree won't be used after this point. + // The allocator has not been moved out and will be moved into `drop_tree`. + unsafe { root.into_dying().drop_tree(ManuallyDrop::take(&mut self.alloc)) }; } else { - // SAFETY: With no root there are no nodes to free, so only the allocator needs - // dropping. `self` is not used after this, and `alloc` is dropped only here. - unsafe { ManuallyDrop::drop(&mut self.alloc) } + // SAFETY: The allocator has not been moved out and won't be used after this point. + unsafe { ManuallyDrop::drop(&mut self.alloc) }; } } } diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index a920254872e19..c4c9148e0abfb 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -2542,6 +2542,114 @@ fn test_into_iter_drop_leak_height_1() { } } +#[test] +fn test_drop_map_elements_multi_level() { + for size in [1, MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2] { + let keys = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let vals = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let map = { + let mut map = BTreeMap::new(); + + (0..size).map(|i| (keys[i].spawn(Panic::Never), vals[i].spawn(Panic::Never))).for_each( + |(k, v)| { + map.insert(k, v); + }, + ); + map + }; + + drop(map); + + for i in 0..size { + assert_eq!(keys[i].dropped(), 1); + assert_eq!(vals[i].dropped(), 1); + } + } +} + +#[test] +fn test_drop_empty_map_allocator() { + use core::alloc::{AllocError, Allocator, AllocatorClone, Layout}; + use core::cell::Cell; + use core::ptr::NonNull; + + struct CountedAllocator<'a>(&'a Cell); + + impl Clone for CountedAllocator<'_> { + fn clone(&self) -> Self { + Self(self.0) + } + } + + impl Drop for CountedAllocator<'_> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1); + } + } + + unsafe impl Allocator for CountedAllocator<'_> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + Global.allocate(layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { Global.deallocate(ptr, layout) } + } + } + + unsafe impl AllocatorClone for CountedAllocator<'_> {} + + let dropped = Cell::new(0); + let map = BTreeMap::::new_in(CountedAllocator(&dropped)); + assert!(map.root.is_none()); + + drop(map); + + assert_eq!(dropped.get(), 1); +} + +#[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_drop_map_elements_multi_level_panic() { + for size in [1, MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2] { + for panic_point in [0, size / 2, size - 1] { + for panic_in_key in [true, false] { + let keys = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let vals = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let map = { + let mut map = BTreeMap::new(); + + (0..size) + .map(|i| { + let key_panic = if panic_in_key && i == panic_point { + Panic::InDrop + } else { + Panic::Never + }; + let val_panic = if !panic_in_key && i == panic_point { + Panic::InDrop + } else { + Panic::Never + }; + (keys[i].spawn(key_panic), vals[i].spawn(val_panic)) + }) + .for_each(|(k, v)| { + map.insert(k, v); + }); + map + }; + + catch_unwind(move || drop(map)).unwrap_err(); + + for i in 0..size { + assert_eq!(keys[i].dropped(), 1); + assert_eq!(vals[i].dropped(), 1); + } + } + } + } +} + #[test] fn test_into_keys() { let map = BTreeMap::from([(1, 'a'), (2, 'b'), (3, 'c')]); diff --git a/library/alloc/src/collections/btree/navigate.rs b/library/alloc/src/collections/btree/navigate.rs index 8b91aacda0b2e..d0a87cf542858 100644 --- a/library/alloc/src/collections/btree/navigate.rs +++ b/library/alloc/src/collections/btree/navigate.rs @@ -852,3 +852,70 @@ impl NodeRef NodeRef { + /// # Safety + /// - `self` must be the root. + /// - The tree shouldn't be accessed anymore. + pub(super) unsafe fn drop_tree(self, alloc: A) { + struct DropGuard { + node: Option>, + alloc: A, + } + + impl Drop for DropGuard { + fn drop(&mut self) { + if let Some(cur) = self.node.take() { + let mut next = + // SAFETY: When unwinding reached this guard, all kvs in cur + // have already been dropped in `drop_kvs`, while the node itself has not + // yet been deallocated. + unsafe { deallocate_and_next_postorder_node(cur, self.alloc.clone()) }; + + while let Some(mut node) = next { + // SAFETY: Every node only calls the function once + unsafe { node.drop_kvs() }; + next = + // SAFETY: `drop_kvs()` above only dropped kvs and the node is still accessible, + // and also each node is deallocated exactly once. + unsafe { deallocate_and_next_postorder_node(node, self.alloc.clone()) } + } + } + } + } + /// Deallocates `node` and returns the next node in postorder traversal + /// + /// # Safety + /// + /// The node must be initialized, all kvs in node must have already + /// been dropped or moved out and its children must have already been deallocated. + /// Also the node can't be touched anymore. + unsafe fn deallocate_and_next_postorder_node( + node: NodeRef, + alloc: A, + ) -> Option> { + // SAFETY: guaranteed by the caller + let parent = unsafe { node.deallocate_and_ascend(alloc) }?; + + Some(match parent.right_kv() { + Ok(kv) => kv.right_edge().descend().first_leaf_edge().into_node().forget_type(), + Err(last_edge) => last_edge.into_node().forget_type(), + }) + } + + let node = self.first_leaf_edge().into_node().forget_type(); + let mut guard = DropGuard { node: Some(node), alloc }; + loop { + // SAFETY: all kvs of each node are dropped once + unsafe { guard.node.as_mut().unwrap().drop_kvs() }; + // SAFETY: Nodes are deallocated in postorder, and each node is deallocated exactly once. + if let Some(next) = unsafe { + deallocate_and_next_postorder_node(guard.node.take().unwrap(), guard.alloc.clone()) + } { + guard.node = Some(next); + } else { + return; + } + } + } +} diff --git a/library/alloc/src/collections/btree/node.rs b/library/alloc/src/collections/btree/node.rs index 47c3fb1d79c9d..2144efc5d7d4d 100644 --- a/library/alloc/src/collections/btree/node.rs +++ b/library/alloc/src/collections/btree/node.rs @@ -429,6 +429,45 @@ impl NodeRef { } ret } + /// Drops all kvs in this node without deallocating the node itself + /// + /// # Safety + /// + /// - kvs in the node must be initialized and must not be accessed after this call. + /// - No kvs in the node can be accessed through any other path while this function is running. + pub(super) unsafe fn drop_kvs(&mut self) { + struct DropGuard<'a, K, V> { + node: &'a NodeRef, + idx: usize, + } + impl<'a, K, V> Drop for DropGuard<'a, K, V> { + fn drop(&mut self) { + let len = self.node.len(); + for i in self.idx..len { + // SAFETY: i < len and the kv has been initialized and + // it won't use the i-th kv more than once. + unsafe { + let kv = Handle::new_kv(ptr::read(self.node), i); + kv.drop_key_val(); + } + } + } + } + let len = self.len(); + for idx in 0..len { + // If `kv.drop_key_val()` panics, the guard would drop + // the rest and skip the panicked kv. + let guard = DropGuard { node: self, idx: idx + 1 }; + // SAFETY: idx < len and the kv has been initialized and + // it won't touch the idx-th more than once + unsafe { + let kv = Handle::new_kv(ptr::read(self), idx); + kv.drop_key_val(); + } + + core::mem::forget(guard); + } + } } impl<'a, K, V, Type> NodeRef, K, V, Type> { diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 62d62db771e52..f95cb5c58eaeb 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -157,7 +157,6 @@ #![feature(pin_coerce_unsized_trait)] #![feature(ptr_alignment_type)] #![feature(ptr_cast_slice)] -#![feature(ptr_internals)] #![feature(ptr_metadata)] #![feature(random)] #![feature(raw_os_error_ty)] diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 8bcad62226a0b..1a151e4eebb33 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2713,7 +2713,24 @@ impl Vec { if first_duplicate_idx == len { return; } + // SAFETY: first_duplicate_idx always in range [1..len) + unsafe { + self.dedup_tail(same_bucket, first_duplicate_idx); + } + } + /// Continues deduplication after the first duplicate is found. + /// + /// The function treats `self[first_duplicate]` as the first duplicated + /// element and drops it. + /// + /// # Safety + /// + /// `1 <= first_duplicate < self.len()` + unsafe fn dedup_tail(&mut self, mut same_bucket: F, first_duplicate: usize) + where + F: FnMut(&mut T, &mut T) -> bool, + { /* INVARIANT: vec.len() > read > write > write-1 >= 0 */ struct FillGapOnDrop<'a, T, A: core::alloc::Allocator> { /* Offset of the element we want to check if it is duplicate */ @@ -2760,16 +2777,20 @@ impl Vec { } } + let start = self.as_mut_ptr(); + let len = self.len(); + /* Drop items while going through Vec, it should be more efficient than * doing slice partition_dedup + truncate */ // Construct gap first and then drop item to avoid memory corruption if `T::drop` panics. let mut gap = - FillGapOnDrop { read: first_duplicate_idx + 1, write: first_duplicate_idx, vec: self }; - // SAFETY: we checked that first_duplicate_idx in bounds before. + FillGapOnDrop { read: first_duplicate + 1, write: first_duplicate, vec: self }; + + // SAFETY: By this function's safety contract, `first_duplicate < self.len()`. // If drop panics, `gap` would remove this item without drop. unsafe { - ptr::drop_in_place(start.add(first_duplicate_idx)); + ptr::drop_in_place(start.add(first_duplicate)); } // SAFETY: Because of the invariant, read_ptr, prev_ptr and write_ptr @@ -3788,8 +3809,142 @@ impl Vec { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn dedup(&mut self) { - self.dedup_by(|a, b| a == b) + // Vectorize the process of finding first duplicate if possible + if let Some(chunk_size) = dedup_simd_chunk_size::() { + // Unlike dedup_by, we don't guarantee the order + // and time in which we call `PartialEq::eq`. + let Some(first_duplicate) = find_first_duplicate(self.as_slice(), chunk_size) else { + return; + }; + + // SAFETY: `1 <= first_duplicate < self.len()` + unsafe { + self.dedup_tail(|a, b| a == b, first_duplicate); + } + } else { + self.dedup_by(|a, b| a == b); + } + } +} + +const fn dedup_simd_chunk_size() -> Option { + // The minimal SIMD width on supported platforms + #[allow(unused)] + const SIMD_WIDTH: usize = 16; + let size = size_of::(); + + // We only have SIMD implementations for these element sizes. + if !matches!(size, 1 | 2 | 4 | 8) { + return None; + } + + let simd_width: Option = cfg_select! { + all(target_arch = "arm", target_feature = "neon") => { + // Packed 64-bit integer lane equality is unavailable on arm neon + if size == 8 { None } else { Some(SIMD_WIDTH) } + } + + all(target_arch = "aarch64", target_feature = "neon") => { + Some(SIMD_WIDTH) + } + + all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "sse4.1" + ) => Some(SIMD_WIDTH), + + all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "sse2" + ) => { + // Packed 64-bit integer lane equality is only available on sse4.1 + if size == 8 { None } else { Some(SIMD_WIDTH) } + } + + all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_feature = "v" + ) => Some(SIMD_WIDTH), + + all( + any(target_arch = "loongarch32", target_arch = "loongarch64"), + target_feature = "lsx" + ) => Some(SIMD_WIDTH), + + _ => None, + }; + + if let Some(simd_width) = simd_width { Some(simd_width * 2 / size) } else { None } +} + +fn find_first_duplicate(v: &[T], chunk_size: usize) -> Option { + let len = v.len(); + if len <= 1 { + return None; + } + + let mut i = 1; + + // The scalar fallback path if the length is not long enough + if len < chunk_size + 1 { + while i < len { + // It keeps the same comparison order as `dedup_tail` + // + // SAFETY: i always in range [1..len) + if unsafe { v.get_unchecked(i) == v.get_unchecked(i - 1) } { + return Some(i); + } + i += 1; + } + return None; + } + + /// # Safety + /// - `start > 0` and `start + chunk_size <= v.len()` + /// - `0 < chunk_size <= 32` + #[inline(always)] + unsafe fn find_duplicate( + v: &[T], + start: usize, + chunk_size: usize, + ) -> Option { + let mut mask: u32 = 0; + debug_assert!( + u32::BITS as usize >= chunk_size, + "the bit width of mask must be greater than or equal to chunk_size" + ); + for j in 0..chunk_size { + // SAFETY: start + j always in range [1..v.len()) + let eq = unsafe { v.get_unchecked(start + j) == v.get_unchecked(start + j - 1) }; + mask |= (eq as u32) << j; + } + if mask != 0 { + return Some(start + mask.trailing_zeros() as usize); + } + None } + + // This loop performs chunk_size comparisons in parallel without early exit, + // which is what allows for SIMD optimization. + while i + chunk_size <= len { + // SAFETY: `i >= 1` and `i + chunk_size <= len` + if let Some(duplicate) = unsafe { find_duplicate(v, i, chunk_size) } { + return Some(duplicate); + } + i += chunk_size; + } + + // This may check the same elements multiple times. + // However we don't guarantee `PartialEq::eq` is only called once per pair. + if i < len { + let start = len - chunk_size; + // SAFETY: `len - chunk_size >= 1` and `start + chunk_size <= len` + if let Some(duplicate) = unsafe { find_duplicate(v, start, chunk_size) } { + return Some(duplicate); + } + } + + None } //////////////////////////////////////////////////////////////////////////////// diff --git a/library/alloctests/benches/vec.rs b/library/alloctests/benches/vec.rs index 656164da72084..a4e151ed42bf7 100644 --- a/library/alloctests/benches/vec.rs +++ b/library/alloctests/benches/vec.rs @@ -688,137 +688,176 @@ fn bench_dedup_slice_truncate(b: &mut Bencher, sz: usize) { }); } -// Measures performance of Vec::dedup on random data. -fn bench_vec_dedup_random(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - random_sorted_fill(0x43, &mut template); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - let vec = black_box(vec); - vec.clear(); - vec.extend_from_slice(&template); - }); -} - -// Measures performance of Vec::dedup when there is no items removed -fn bench_vec_dedup_none(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - template.chunks_exact_mut(2).for_each(|w| { - w[0] = black_box(0); - w[1] = black_box(5); - }); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - // Unlike other benches of `dedup` - // this doesn't reinitialize vec - // because we measure how efficient dedup is - // when no memory written - }); -} - -// Measures performance of Vec::dedup when there is all items removed -fn bench_vec_dedup_all(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - template.iter_mut().for_each(|w| { - *w = black_box(0); - }); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - let vec = black_box(vec); - vec.clear(); - vec.extend_from_slice(&template); - }); -} - #[bench] fn bench_dedup_slice_truncate_100(b: &mut Bencher) { bench_dedup_slice_truncate(b, 100); } -#[bench] -fn bench_dedup_random_100(b: &mut Bencher) { - bench_vec_dedup_random(b, 100); -} - -#[bench] -fn bench_dedup_none_100(b: &mut Bencher) { - bench_vec_dedup_none(b, 100); -} - -#[bench] -fn bench_dedup_all_100(b: &mut Bencher) { - bench_vec_dedup_all(b, 100); -} - #[bench] fn bench_dedup_slice_truncate_1000(b: &mut Bencher) { bench_dedup_slice_truncate(b, 1000); } #[bench] -fn bench_dedup_random_1000(b: &mut Bencher) { - bench_vec_dedup_random(b, 1000); +fn bench_dedup_slice_truncate_10000(b: &mut Bencher) { + bench_dedup_slice_truncate(b, 10000); } - #[bench] -fn bench_dedup_none_1000(b: &mut Bencher) { - bench_vec_dedup_none(b, 1000); +fn bench_dedup_slice_truncate_100000(b: &mut Bencher) { + bench_dedup_slice_truncate(b, 100000); } -#[bench] -fn bench_dedup_all_1000(b: &mut Bencher) { - bench_vec_dedup_all(b, 1000); +#[derive(Clone, Copy)] +enum DedupDist { + Random, + None, + All, } -#[bench] -fn bench_dedup_slice_truncate_10000(b: &mut Bencher) { - bench_dedup_slice_truncate(b, 10000); +trait DedupBenchValue { + fn bench_bytes(&self) -> usize; } -#[bench] -fn bench_dedup_random_10000(b: &mut Bencher) { - bench_vec_dedup_random(b, 10000); + +macro_rules! dedup_bench_value_by_size { + ($($ty:ty),+ $(,)?) => { + $( + impl DedupBenchValue for $ty { + fn bench_bytes(&self) -> usize { + size_of_val(self) + } + } + )+ + }; } -#[bench] -fn bench_dedup_none_10000(b: &mut Bencher) { - bench_vec_dedup_none(b, 10000); +dedup_bench_value_by_size!(u8, u16, u32, u64, [u64; 4]); + +impl DedupBenchValue for String { + fn bench_bytes(&self) -> usize { + self.len() + } } -#[bench] -fn bench_dedup_all_10000(b: &mut Bencher) { - bench_vec_dedup_all(b, 10000); +fn bench_vec_dedup( + b: &mut Bencher, + dist: DedupDist, + sz: usize, + make: impl Fn(u32) -> T, +) { + let template: Vec = match dist { + DedupDist::Random => { + let mut seeds = vec![0u32; sz]; + random_sorted_fill(0x43, &mut seeds); + let mut v: Vec = seeds.into_iter().map(make).collect(); + v.sort(); + v + } + DedupDist::None => { + (0..sz).map(|i| make(black_box(if i % 2 == 0 { 0 } else { 5 }))).collect() + } + DedupDist::All => (0..sz).map(|_| make(black_box(0))).collect(), + }; + b.bytes = template.iter().map(DedupBenchValue::bench_bytes).sum::() as u64; + let mut vec = template.clone(); + b.iter(|| { + let vec = black_box(&mut vec); + vec.dedup(); + black_box(vec.first()); + // Unlike other benches of `dedup` + // `DedupDist::None` doesn't reinitialize vec + // because we measure how efficient dedup is + // when no memory written + if !matches!(dist, DedupDist::None) { + black_box(vec).clone_from(&template); + } + }); } -#[bench] -fn bench_dedup_slice_truncate_100000(b: &mut Bencher) { - bench_dedup_slice_truncate(b, 100000); +fn dedup_gen_string(x: u32) -> String { + const DIGITS: usize = 10; + let mut s = String::with_capacity(LEN); + s.extend(std::iter::repeat_n('p', LEN - DIGITS)); + s.push_str(&format!("{x:0DIGITS$}")); + s } -#[bench] -fn bench_dedup_random_100000(b: &mut Bencher) { - bench_vec_dedup_random(b, 100000); + +fn dedup_gen_wide(x: u32) -> [u64; 4] { + [x as u64, (x as u64) << 8, (x as u64) << 16, (x as u64) << 24] } -#[bench] -fn bench_dedup_none_100000(b: &mut Bencher) { - bench_vec_dedup_none(b, 100000); +macro_rules! dedup_typed { + ($($name:ident: $gen:expr, $dist:ident, $sz:literal;)*) => { + $( + #[bench] + fn $name(b: &mut Bencher) { + bench_vec_dedup(b, DedupDist::$dist, $sz, $gen); + } + )* + }; } -#[bench] -fn bench_dedup_all_100000(b: &mut Bencher) { - bench_vec_dedup_all(b, 100000); +dedup_typed! { + bench_dedup_random_100: |x| x, Random, 100; + bench_dedup_none_100: |x| x, None, 100; + bench_dedup_all_100: |x| x, All, 100; + bench_dedup_random_1000: |x| x, Random, 1000; + bench_dedup_none_1000: |x| x, None, 1000; + bench_dedup_all_1000: |x| x, All, 1000; + bench_dedup_random_10000: |x| x, Random, 10000; + bench_dedup_none_10000: |x| x, None, 10000; + bench_dedup_all_10000: |x| x, All, 10000; + bench_dedup_random_100000: |x| x, Random, 100000; + bench_dedup_none_100000: |x| x, None, 100000; + bench_dedup_all_100000: |x| x, All, 100000; + bench_dedup_string_short_random_100: dedup_gen_string::<10>, Random, 100; + bench_dedup_string_short_none_100: dedup_gen_string::<10>, None, 100; + bench_dedup_string_short_all_100: dedup_gen_string::<10>, All, 100; + bench_dedup_string_short_random_1000: dedup_gen_string::<10>, Random, 1000; + bench_dedup_string_short_none_1000: dedup_gen_string::<10>, None, 1000; + bench_dedup_string_short_all_1000: dedup_gen_string::<10>, All, 1000; + bench_dedup_string_short_random_10000: dedup_gen_string::<10>, Random, 10000; + bench_dedup_string_short_none_10000: dedup_gen_string::<10>, None, 10000; + bench_dedup_string_short_all_10000: dedup_gen_string::<10>, All, 10000; + bench_dedup_string_medium_random_100: dedup_gen_string::<64>, Random, 100; + bench_dedup_string_medium_none_100: dedup_gen_string::<64>, None, 100; + bench_dedup_string_medium_all_100: dedup_gen_string::<64>, All, 100; + bench_dedup_string_medium_random_1000: dedup_gen_string::<64>, Random, 1000; + bench_dedup_string_medium_none_1000: dedup_gen_string::<64>, None, 1000; + bench_dedup_string_medium_all_1000: dedup_gen_string::<64>, All, 1000; + bench_dedup_string_medium_random_10000: dedup_gen_string::<64>, Random, 10000; + bench_dedup_string_medium_none_10000: dedup_gen_string::<64>, None, 10000; + bench_dedup_string_medium_all_10000: dedup_gen_string::<64>, All, 10000; + bench_dedup_string_long_random_100: dedup_gen_string::<1024>, Random, 100; + bench_dedup_string_long_none_100: dedup_gen_string::<1024>, None, 100; + bench_dedup_string_long_all_100: dedup_gen_string::<1024>, All, 100; + bench_dedup_string_long_random_1000: dedup_gen_string::<1024>, Random, 1000; + bench_dedup_string_long_none_1000: dedup_gen_string::<1024>, None, 1000; + bench_dedup_string_long_all_1000: dedup_gen_string::<1024>, All, 1000; + bench_dedup_string_long_random_10000: dedup_gen_string::<1024>, Random, 10000; + bench_dedup_string_long_none_10000: dedup_gen_string::<1024>, None, 10000; + bench_dedup_string_long_all_10000: dedup_gen_string::<1024>, All, 10000; + bench_dedup_u8_random_1000: |x| x as u8, Random, 1000; + bench_dedup_u8_none_1000: |x| x as u8, None, 1000; + bench_dedup_u8_all_1000: |x| x as u8, All, 1000; + bench_dedup_u8_random_10000: |x| x as u8, Random, 10000; + bench_dedup_u8_none_10000: |x| x as u8, None, 10000; + bench_dedup_u8_all_10000: |x| x as u8, All, 10000; + bench_dedup_u16_random_1000: |x| x as u16, Random, 1000; + bench_dedup_u16_none_1000: |x| x as u16, None, 1000; + bench_dedup_u16_all_1000: |x| x as u16, All, 1000; + bench_dedup_u16_random_10000: |x| x as u16, Random, 10000; + bench_dedup_u16_none_10000: |x| x as u16, None, 10000; + bench_dedup_u16_all_10000: |x| x as u16, All, 10000; + bench_dedup_u64_random_1000: |x| x as u64, Random, 1000; + bench_dedup_u64_none_1000: |x| x as u64, None, 1000; + bench_dedup_u64_all_1000: |x| x as u64, All, 1000; + bench_dedup_u64_random_10000: |x| x as u64, Random, 10000; + bench_dedup_u64_none_10000: |x| x as u64, None, 10000; + bench_dedup_u64_all_10000: |x| x as u64, All, 10000; + bench_dedup_wide_random_1000: dedup_gen_wide, Random, 1000; + bench_dedup_wide_none_1000: dedup_gen_wide, None, 1000; + bench_dedup_wide_all_1000: dedup_gen_wide, All, 1000; + bench_dedup_wide_random_10000: dedup_gen_wide, Random, 10000; + bench_dedup_wide_none_10000: dedup_gen_wide, None, 10000; + bench_dedup_wide_all_10000: dedup_gen_wide, All, 10000; } #[bench] diff --git a/library/alloctests/tests/vec.rs b/library/alloctests/tests/vec.rs index 787452df7f680..cfb6a6bd03824 100644 --- a/library/alloctests/tests/vec.rs +++ b/library/alloctests/tests/vec.rs @@ -2534,6 +2534,94 @@ fn test_vec_dedup_panicking() { } } +#[test] +fn test_vec_dedup_long() { + fn check(template: &[T]) { + let mut expected = template.to_vec(); + let (kept, _) = expected.partition_dedup(); + let kept_len = kept.len(); + expected.truncate(kept_len); + + let mut vec = template.to_vec(); + vec.dedup(); + assert_eq!(vec, expected, "input: {template:?}"); + } + + macro_rules! check_type { + ($ty:ty) => { + for len in [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65] { + let distinct: Vec<$ty> = (0..len).map(|x| x as $ty).collect(); + check(&distinct); + check(&vec![7 as $ty; len]); + + for dup_at in 1..len { + let mut v = distinct.clone(); + v[dup_at] = v[dup_at - 1]; + check(&v); + + if dup_at + 1 < len { + let mut w = v.clone(); + w[len - 1] = w[len - 2]; + check(&w); + } + } + } + }; + } + + check_type!(u8); + check_type!(u16); + check_type!(u32); + check_type!(u64); +} + +#[test] +fn test_vec_dedup_long_boxed() { + for len in [9, 10, 16, 17, 24, 25, 33] { + for dup_at in 1..len { + let mut v: Vec> = (0..len as u32).map(Box::new).collect(); + v[dup_at] = Box::new(*v[dup_at - 1]); + let expected: Vec = (0..len as u32).filter(|&x| x != dup_at as u32).collect(); + v.dedup(); + let got: Vec = v.iter().map(|b| **b).collect(); + assert_eq!(got, expected, "len={len} dup_at={dup_at}"); + } + } +} + +#[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_vec_dedup_eq_panics_for_simd_sized_type() { + static EQ_COUNT: AtomicU32 = AtomicU32::new(0); + static DROP_COUNT: AtomicU32 = AtomicU32::new(0); + + #[derive(Debug)] + #[repr(transparent)] + struct Panic(u32); + + impl PartialEq for Panic { + fn eq(&self, other: &Self) -> bool { + if EQ_COUNT.fetch_add(1, Ordering::SeqCst) == 2 { + panic!("comparison panic"); + } + self.0 == other.0 + } + } + + impl Drop for Panic { + fn drop(&mut self) { + DROP_COUNT.fetch_add(1, Ordering::SeqCst); + } + } + + EQ_COUNT.store(0, Ordering::SeqCst); + DROP_COUNT.store(0, Ordering::SeqCst); + let mut vec: Vec<_> = [0, 0, 1, 2, 3, 4, 5, 6, 7].into_iter().map(Panic).collect(); + assert!(catch_unwind(AssertUnwindSafe(|| vec.dedup())).is_err()); + drop(vec); + assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 9); +} + // Regression test for issue #82533 #[test] #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] diff --git a/library/core/src/panic/unwind_safe.rs b/library/core/src/panic/unwind_safe.rs index 60c833db33e15..79141ec4370d7 100644 --- a/library/core/src/panic/unwind_safe.rs +++ b/library/core/src/panic/unwind_safe.rs @@ -5,7 +5,7 @@ use crate::future::Future; use crate::marker::PointeeSized; use crate::ops::{Deref, DerefMut}; use crate::pin::Pin; -use crate::ptr::{NonNull, Unique}; +use crate::ptr::NonNull; use crate::task::{Context, Poll}; /// A marker trait which represents "panic safe" types in Rust. @@ -186,8 +186,6 @@ impl UnwindSafe for &T {} impl UnwindSafe for *const T {} #[stable(feature = "catch_unwind", since = "1.9.0")] impl UnwindSafe for *mut T {} -#[unstable(feature = "ptr_internals", issue = "none")] -impl UnwindSafe for Unique {} #[stable(feature = "nonnull", since = "1.25.0")] impl UnwindSafe for NonNull {} #[stable(feature = "catch_unwind", since = "1.9.0")] diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 9019e3a486921..27951a65f6c84 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -447,10 +447,6 @@ mod non_null; #[stable(feature = "nonnull", since = "1.25.0")] pub use non_null::NonNull; -mod unique; -#[unstable(feature = "ptr_internals", issue = "none")] -pub use unique::Unique; - mod const_ptr; mod mut_ptr; diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index ae6d194ff6929..dc67d94d6e08e 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -4,7 +4,6 @@ use crate::marker::{Destruct, PointeeSized, Unsize}; use crate::mem::{MaybeUninit, SizedTypeProperties, transmute}; use crate::num::NonZero; use crate::ops::{CoerceUnsized, DispatchFromDyn}; -use crate::ptr::Unique; use crate::slice::{self, SliceIndex}; use crate::ub_checks::assert_unsafe_precondition; use crate::{fmt, hash, intrinsics, mem, ptr}; @@ -1691,15 +1690,6 @@ impl hash::Hash for NonNull { } } -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From> for NonNull { - #[inline] - fn from(unique: Unique) -> Self { - unique.as_non_null_ptr() - } -} - #[stable(feature = "nonnull", since = "1.25.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] const impl From<&mut T> for NonNull { diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs deleted file mode 100644 index 5e3fe666ac9ba..0000000000000 --- a/library/core/src/ptr/unique.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::clone::TrivialClone; -use crate::fmt; -use crate::marker::{PhantomData, PointeeSized, Unsize}; -use crate::ops::{CoerceUnsized, DispatchFromDyn}; -use crate::ptr::NonNull; - -/// A wrapper around a raw non-null `*mut T` that indicates that the possessor -/// of this wrapper owns the referent. Useful for building abstractions like -/// `Box`, `Vec`, `String`, and `HashMap`. -/// -/// Unlike `*mut T`, `Unique` behaves "as if" it were an instance of `T`. -/// It implements `Send`/`Sync` if `T` is `Send`/`Sync`. It also implies -/// the kind of strong aliasing guarantees an instance of `T` can expect: -/// the referent of the pointer should not be modified without a unique path to -/// its owning Unique. -/// -/// If you're uncertain of whether it's correct to use `Unique` for your purposes, -/// consider using `NonNull`, which has weaker semantics. -/// -/// Unlike `*mut T`, the pointer must always be non-null, even if the pointer -/// is never dereferenced. This is so that enums may use this forbidden value -/// as a discriminant -- `Option>` has the same size as `Unique`. -/// However the pointer may still dangle if it isn't dereferenced. -/// -/// Unlike `*mut T`, `Unique` is covariant over `T`. This should always be correct -/// for any type which upholds Unique's aliasing requirements. -#[unstable( - feature = "ptr_internals", - issue = "none", - reason = "use `NonNull` instead and consider `PhantomData` \ - (if you also use `#[may_dangle]`), `Send`, and/or `Sync`" -)] -#[doc(hidden)] -#[repr(transparent)] -pub struct Unique { - pointer: NonNull, - // NOTE: this marker has no consequences for variance, but is necessary - // for dropck to understand that we logically own a `T`. - // - // For details, see: - // https://rust-lang.github.io/rfcs/0769-sound-generic-drop.html#phantom-data - _marker: PhantomData, -} - -/// `Unique` pointers are `Send` if `T` is `Send` because the data they -/// reference is unaliased. Note that this aliasing invariant is -/// unenforced by the type system; the abstraction using the -/// `Unique` must enforce it. -#[unstable(feature = "ptr_internals", issue = "none")] -unsafe impl Send for Unique {} - -/// `Unique` pointers are `Sync` if `T` is `Sync` because the data they -/// reference is unaliased. Note that this aliasing invariant is -/// unenforced by the type system; the abstraction using the -/// `Unique` must enforce it. -#[unstable(feature = "ptr_internals", issue = "none")] -unsafe impl Sync for Unique {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Unique { - /// Creates a new `Unique` that is dangling, but well-aligned. - /// - /// This is useful for initializing types which lazily allocate, like - /// `Vec::new` does. - /// - /// Note that the address of the returned pointer may potentially - /// be that of a valid pointer, which means this must not be used - /// as a "not yet initialized" sentinel value. - /// Types that lazily allocate must track initialization by some other means. - #[must_use] - #[inline] - pub const fn dangling() -> Self { - // FIXME(const-hack) replace with `From` - Unique { pointer: NonNull::dangling(), _marker: PhantomData } - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Unique { - /// Creates a new `Unique`. - /// - /// # Safety - /// - /// `ptr` must be non-null. - #[inline] - pub const unsafe fn new_unchecked(ptr: *mut T) -> Self { - // SAFETY: the caller must guarantee that `ptr` is non-null. - unsafe { Unique { pointer: NonNull::new_unchecked(ptr), _marker: PhantomData } } - } - - /// Creates a new `Unique` if `ptr` is non-null. - #[inline] - pub const fn new(ptr: *mut T) -> Option { - if let Some(pointer) = NonNull::new(ptr) { - Some(Unique { pointer, _marker: PhantomData }) - } else { - None - } - } - - /// Create a new `Unique` from a `NonNull` in const context. - #[inline] - pub const fn from_non_null(pointer: NonNull) -> Self { - Unique { pointer, _marker: PhantomData } - } - - /// Acquires the underlying `*mut` pointer. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn as_ptr(self) -> *mut T { - self.pointer.as_ptr() - } - - /// Acquires the underlying `*mut` pointer. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn as_non_null_ptr(self) -> NonNull { - self.pointer - } - - /// Dereferences the content. - /// - /// The resulting lifetime is bound to self so this behaves "as if" - /// it were actually an instance of T that is getting borrowed. If a longer - /// (unbound) lifetime is needed, use `&*my_ptr.as_ptr()`. - #[must_use] - #[inline] - pub const unsafe fn as_ref(&self) -> &T { - // SAFETY: the caller must guarantee that `self` meets all the - // requirements for a reference. - unsafe { self.pointer.as_ref() } - } - - /// Mutably dereferences the content. - /// - /// The resulting lifetime is bound to self so this behaves "as if" - /// it were actually an instance of T that is getting borrowed. If a longer - /// (unbound) lifetime is needed, use `&mut *my_ptr.as_ptr()`. - #[must_use] - #[inline] - pub const unsafe fn as_mut(&mut self) -> &mut T { - // SAFETY: the caller must guarantee that `self` meets all the - // requirements for a mutable reference. - unsafe { self.pointer.as_mut() } - } - - /// Casts to a pointer of another type. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn cast(self) -> Unique { - // FIXME(const-hack): replace with `From` - // SAFETY: is `NonNull` - Unique { pointer: self.pointer.cast(), _marker: PhantomData } - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Clone for Unique { - #[inline] - fn clone(&self) -> Self { - *self - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Copy for Unique {} - -#[doc(hidden)] -#[unstable(feature = "trivial_clone", issue = "none")] -unsafe impl TrivialClone for Unique {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl CoerceUnsized> for Unique where T: Unsize {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl DispatchFromDyn> for Unique where T: Unsize {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl fmt::Debug for Unique { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.as_ptr(), f) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl fmt::Pointer for Unique { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.as_ptr(), f) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From<&mut T> for Unique { - /// Converts a `&mut T` to a `Unique`. - /// - /// This conversion is infallible since references cannot be null. - #[inline] - fn from(reference: &mut T) -> Self { - Self::from(NonNull::from(reference)) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From> for Unique { - /// Converts a `NonNull` to a `Unique`. - /// - /// This conversion is infallible since `NonNull` cannot be null. - #[inline] - fn from(pointer: NonNull) -> Self { - Unique::from_non_null(pointer) - } -} diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 139275ed2e1c2..1171f1c05b25c 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3135,6 +3135,76 @@ pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> fs_imp::rename(from.as_ref(), to.as_ref()) } +/// Renames a file or directory to a new name, returning [`AlreadyExists`] if `to` already +/// exists. +/// +/// Unlike [`fs::rename`], the destination is never overwritten. The check happens as part of the +/// same operation, which avoids check-then-rename races. +/// +/// If the system crashes in the middle, it may be possible to end up with both copies of the file +/// existing, but it should not be possible to end up with zero copies, or with a half-written file. +/// +/// This will not work if the new name is on a different mount point. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to: +/// +/// * [renameat2] with `RENAME_NOREPLACE` on Linux and Android +/// * `renamex_np` with `RENAME_EXCL` on Apple platforms +/// * [MoveFileExW] without the `MOVEFILE_REPLACE_EXISTING` flag, with a fallback to +/// `SetFileInformationByHandle`, on Windows +/// * `link` followed by `unlink` on other Unix platforms and on WASI +/// * Otherwise, this function returns [`Unsupported`] +/// +/// The `link` and `unlink` fallback is also used at runtime on Linux, Android and Apple platforms +/// when the kernel or the filesystem does not support the flag, so which of these applies can +/// differ between filesystems on the same machine. +/// +/// Note that, this [may change in the future][changes]. +/// +/// [changes]: io#platform-specific-behavior +/// [renameat2]: https://man7.org/linux/man-pages/man2/renameat2.2.html +/// [MoveFileExW]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw +/// [`AlreadyExists`]: crate::io::ErrorKind::AlreadyExists +/// [`Unsupported`]: crate::io::ErrorKind::Unsupported +/// [`fs::rename`]: crate::fs::rename +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `from` does not exist. +/// * The user lacks permissions to view contents. +/// * `from` and `to` are on separate filesystems. +/// * `to` already exists. +/// * The platform does not support this operation. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(rename_noreplace)] +/// use std::fs; +/// use std::io::ErrorKind; +/// +/// fn main() -> std::io::Result<()> { +/// // Renames a.txt to b.txt, b.txt does not exist yet. +/// fs::rename_noreplace("a.txt", "b.txt")?; +/// +/// // b.txt exists now, so this fails instead of silently overwriting it. +/// let err = fs::rename_noreplace("c.txt", "b.txt").unwrap_err(); +/// assert_eq!(err.kind(), ErrorKind::AlreadyExists); +/// Ok(()) +/// } +/// ``` +#[doc(alias = "renameat2", alias = "renamex_np", alias = "MoveFileEx", alias = "RENAME_NOREPLACE")] +#[unstable(feature = "rename_noreplace", issue = "161427")] +#[cfg_attr(not(test), rustc_diagnostic_item = "fs_rename_noreplace")] +pub fn rename_noreplace, Q: AsRef>(from: P, to: Q) -> io::Result<()> { + fs_imp::rename_noreplace(from.as_ref(), to.as_ref()) +} + /// Copies the contents of one file to another. This function will also /// copy the permission bits of the original file to the destination file. /// diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 54a6cf3904202..bb46c5c869aea 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2664,6 +2664,98 @@ fn test_rename_symlink() { assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); } +#[test] +fn test_rename_noreplace() { + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_file.txt"); + let target_path = tmpdir.join("target_file.txt"); + + fs::write(&source_path, b"source hello world").unwrap(); + + fs::rename_noreplace(&source_path, &target_path).unwrap(); + assert!(!source_path.exists()); + assert_eq!(fs::read(&target_path).unwrap(), b"source hello world"); +} + +#[test] +fn test_rename_noreplace_existing_file() { + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_file.txt"); + let target_path = tmpdir.join("target_file.txt"); + + fs::write(&source_path, b"source hello world").unwrap(); + fs::write(&target_path, b"target hello world").unwrap(); + + let err = fs::rename_noreplace(&source_path, &target_path).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::AlreadyExists); + // Make sure the failed rename left both files untouched. + assert_eq!(fs::read(&source_path).unwrap(), b"source hello world"); + assert_eq!(fs::read(&target_path).unwrap(), b"target hello world"); +} + +#[test] +fn test_rename_noreplace_directory_to_empty_directory() { + // Renaming a directory over an existing empty directory should fail; + // plain `rename` would succeed here. + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_directory"); + let target_path = tmpdir.join("target_directory"); + + fs::create_dir(&source_path).unwrap(); + fs::create_dir(&target_path).unwrap(); + + let err = fs::rename_noreplace(&source_path, &target_path).unwrap_err(); + assert_matches!( + err.kind(), + // A native no-replace rename returns `AlreadyExists`. The `link`/`unlink` + // fallback cannot rename directories and returns `PermissionDenied`. + ErrorKind::AlreadyExists | ErrorKind::PermissionDenied, + "Expected AlreadyExists or PermissionDenied error, got {err}" + ); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple", windows))] +fn test_rename_noreplace_directory() { + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_directory"); + let target_path = tmpdir.join("target_directory"); + + fs::create_dir(&source_path).unwrap(); + fs::write(source_path.join("file.txt"), b"hello world").unwrap(); + + match fs::rename_noreplace(&source_path, &target_path) { + Ok(()) => { + assert!(!source_path.exists()); + assert_eq!(fs::read(target_path.join("file.txt")).unwrap(), b"hello world"); + } + // Without a native no-replace rename (Linux before 3.15), Unix + // platforms fall back to `link`/`unlink`, and it rejects directories. + Err(err) if cfg!(unix) && err.kind() == ErrorKind::PermissionDenied => { + assert!(source_path.exists()); + assert!(!target_path.exists()); + } + Err(err) => panic!("unexpected error: {err:?}"), + } +} + +#[test] +fn test_rename_noreplace_symlink() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + symlink_file(not_exist, &original).unwrap(); + fs::rename_noreplace(&original, &dest).unwrap(); + // Make sure that renaming `original` to `dest` preserves the symlink. + assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); +} + #[test] #[cfg(windows)] fn test_rename_junction() { diff --git a/library/std/src/sync/oneshot.rs b/library/std/src/sync/oneshot.rs index c905b7363e2a5..4fb21bf1a714f 100644 --- a/library/std/src/sync/oneshot.rs +++ b/library/std/src/sync/oneshot.rs @@ -86,9 +86,8 @@ pub struct Sender { inner: mpmc::Sender, } -// SAFETY: Since the only methods in which synchronization must occur take full ownership of the -// [`Sender`], it is perfectly safe to share a `&Sender` between threads (as it is effectively -// useless without ownership). +// SAFETY: Sending a value requires ownership of the sender. Shared references only inspect +// channel state, so they cannot send a value of a non-`Send` type from another thread. #[unstable(feature = "oneshot_channel", issue = "143674")] unsafe impl Sync for Sender {} @@ -119,6 +118,20 @@ impl Sender { pub fn send(self, t: T) -> Result<(), SendError> { self.inner.send(t) } + + /// Returns `true` if the corresponding [`Receiver`] has been dropped. + /// + /// If this method returns `true`, [`send`](Self::send) is guaranteed to return an error. + /// + /// On the receiving side, [`Receiver::is_ready`] checks whether a value is available without + /// consuming the receiver. + /// + /// A return value of `false` does not guarantee that a later call to [`send`](Self::send) will + /// succeed. The receiver may be dropped immediately after this method returns. + #[unstable(feature = "oneshot_channel", issue = "143674")] + pub fn is_closed(&self) -> bool { + self.inner.is_disconnected() + } } #[unstable(feature = "oneshot_channel", issue = "143674")] diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 89f6989b9adbe..650f7e884c41d 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -594,6 +594,10 @@ pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { unsupported() } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index ca5c28dcd88bd..e15841b363d18 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -78,6 +78,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { with_native_path(old, &|old| with_native_path(new, &|new| imp::rename(old, new))) } +pub fn rename_noreplace(old: &Path, new: &Path) -> io::Result<()> { + with_native_path(old, &|old| with_native_path(new, &|new| imp::rename_noreplace(old, new))) +} + pub fn remove_dir(path: &Path) -> io::Result<()> { with_native_path(path, &imp::rmdir) } diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 91cd90a6b0cb3..06d7356b2a519 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -324,6 +324,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { moto_rt::fs::rename(old, new).map_err(map_motor_error) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn rmdir(path: &Path) -> io::Result<()> { let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; moto_rt::fs::rmdir(path).map_err(map_motor_error) diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index dfdcacdcdf277..849a1657b761d 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -530,6 +530,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { Ok(()) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { // Solid does not support symlinks set_perm_nofollow(p, perm) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 5d0c219586e0e..ef902931533d3 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -474,6 +474,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { f.set_file_info(new_info) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { // UEFI does not support symlinks set_perm_nofollow(p, perm) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 0adc0b9136f63..342916cdcfee4 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -69,7 +69,7 @@ pub use crate::sys::fs::common::exists; pub use crate::sys::fs::common::{ExtraHomeDirs, ExtraMediaDirs}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::time::SystemTime; -#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[cfg(any(target_os = "linux", target_os = "android"))] use crate::sys::weak::syscall; #[cfg(target_os = "android")] use crate::sys::weak::weak; @@ -1862,6 +1862,59 @@ pub fn rename(old: &CStr, new: &CStr) -> io::Result<()> { cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ()) } +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + syscall!( + fn renameat2( + olddirfd: c_int, + oldpath: *const libc::c_char, + newdirfd: c_int, + newpath: *const libc::c_char, + flags: libc::c_uint, + ) -> c_int; + ); + + // `RENAME_NOREPLACE` is `c_uint` on linux and `c_int` on Android, so casting is needed here. + let flags = libc::RENAME_NOREPLACE as libc::c_uint; + let res = cvt(unsafe { + renameat2(libc::AT_FDCWD, old.as_ptr(), libc::AT_FDCWD, new.as_ptr(), flags) + }); + + match res { + Ok(_) => Ok(()), + Err(err) => match err.raw_os_error() { + // The kernel predates `renameat2`. + Some(libc::ENOSYS) => link_then_unlink(old, new), + // The filesystem does not support `RENAME_NOREPLACE`. + // `EINVAL` also covers misuse, so report it unchanged unless the fallback found `new`. + Some(libc::EINVAL) => match link_then_unlink(old, new) { + Err(fallback) if fallback.raw_os_error() != Some(libc::EEXIST) => Err(err), + res => res, + }, + _ => Err(err), + }, + } +} + +#[cfg(target_vendor = "apple")] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + let res = cvt(unsafe { libc::renamex_np(old.as_ptr(), new.as_ptr(), libc::RENAME_EXCL) }); + match res { + Ok(_) => Ok(()), + Err(err) => match err.raw_os_error() { + // The filesystem does not support `RENAME_EXCL`. + // Unlike Linux, Darwin keeps `EINVAL` for misuse only. + Some(libc::ENOTSUP) => link_then_unlink(old, new), + _ => Err(err), + }, + } +} + +#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + link_then_unlink(old, new) +} + pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } @@ -2062,6 +2115,11 @@ pub fn canonicalize(path: &CStr) -> io::Result { }))) } +fn link_then_unlink(old: &CStr, new: &CStr) -> io::Result<()> { + link(old, new)?; + unlink(old) +} + fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { use crate::fs::File; use crate::sys::fs::common::NOT_FILE_ERROR; diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 26677c7c7410a..f63553a543669 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -309,6 +309,10 @@ pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { unsupported() } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 5043a4daa83b4..ec5da582140cc 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -12,8 +12,8 @@ use crate::sys::{unsupported, unsupported_err}; #[path = "unsupported.rs"] mod unsupported_fs; pub use unsupported_fs::{ - Dir, DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, - symlink, unlink, + Dir, DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, + rename_noreplace, rmdir, symlink, unlink, }; pub use crate::sys::fs::common::{ExtraHomeDirs, ExtraMediaDirs}; diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index c4e0bb6ecf51f..548b2d4b6f00f 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1303,8 +1303,8 @@ pub fn unlink(path: &WCStr) -> io::Result<()> { } } -pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { - if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 { +fn rename_inner(old: &WCStr, new: &WCStr, move_flags: u32, rename_flags: u32) -> io::Result<()> { + if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), move_flags) } == 0 { let err = api::get_last_error(); // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move // the file while ignoring the readonly attribute. @@ -1336,10 +1336,8 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { return Err(io::ErrorKind::OutOfMemory.into()); } - (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { - Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS - | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, - }); + (&raw mut (*file_rename_info).Anonymous) + .write(c::FILE_RENAME_INFO_0 { Flags: rename_flags }); (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); // Don't include the NULL in the size @@ -1374,6 +1372,21 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { Ok(()) } +pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { + rename_inner( + old, + new, + c::MOVEFILE_REPLACE_EXISTING, + c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, + ) +} + +pub fn rename_noreplace(old: &WCStr, new: &WCStr) -> io::Result<()> { + // Without `MOVEFILE_REPLACE_EXISTING`, `MoveFileExW` fails if the destination + // exists. + rename_inner(old, new, 0, c::FILE_RENAME_FLAG_POSIX_SEMANTICS) +} + pub fn rmdir(p: &WCStr) -> io::Result<()> { cvt(unsafe { c::RemoveDirectoryW(p.as_ptr()) })?; Ok(()) diff --git a/library/std/tests/sync/oneshot.rs b/library/std/tests/sync/oneshot.rs index 0197369869d80..b7bdaedc62d36 100644 --- a/library/std/tests/sync/oneshot.rs +++ b/library/std/tests/sync/oneshot.rs @@ -9,6 +9,7 @@ use std::{mem, thread}; #[test] fn is_ready_after_send() { let (sender, receiver) = oneshot::channel(); + assert!(!sender.is_closed()); assert_eq!(receiver.is_ready(), Ok(false)); sender.send(String::from("hello")).unwrap(); @@ -126,6 +127,7 @@ fn send_error() { mem::drop(receiver); + assert!(sender.is_closed()); let send_error = sender.send(32u128).unwrap_err(); assert_eq!(send_error.0, 32); } diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 4ac0f6ea85679..8885a7bd89afb 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -557,11 +557,14 @@ impl CommandLineStep for Llvm { // which saves both memory during parallel links and overall disk space // for the tools. We don't do this on every platform as it doesn't work // equally well everywhere. - if link_shared { - cfg.define("LLVM_LINK_LLVM_DYLIB", "ON"); - // Keep the pre-LLVM23 behavior for now. - cfg.define("LLVM_VERSIONED_DYLIB_NAME_ON_DARWIN", "OFF"); - } + // + // Under normal circumstances, the dylib LINK and BUILD vars below should + // be set to the same value. See #160597 + let llvm_dylib = if link_shared { "ON" } else { "OFF" }; + cfg.define("LLVM_LINK_LLVM_DYLIB", llvm_dylib); + cfg.define("LLVM_BUILD_LLVM_DYLIB", llvm_dylib); + // Keep the pre-LLVM23 behavior for now. + cfg.define("LLVM_VERSIONED_DYLIB_NAME_ON_DARWIN", "OFF"); if (target.starts_with("csky") || target.starts_with("riscv") diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 9b878307afc96..2b19015b9c2b0 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -977,7 +977,6 @@ ui/feature-gates/issue-43106-gating-of-proc_macro_derive.rs ui/feature-gates/issue-43106-gating-of-stable.rs ui/feature-gates/issue-43106-gating-of-test.rs ui/feature-gates/issue-43106-gating-of-unstable.rs -ui/feature-gates/issue-49983-see-issue-0.rs ui/fmt/issue-103826.rs ui/fmt/issue-104142.rs ui/fmt/issue-23781.rs diff --git a/tests/debuginfo/marker-types.rs b/tests/debuginfo/marker-types.rs index 3c9cba608178c..8e250944a4212 100644 --- a/tests/debuginfo/marker-types.rs +++ b/tests/debuginfo/marker-types.rs @@ -21,17 +21,9 @@ //@ cdb-check: [capacity] : 0x4 [Type: unsigned [...]] //@ cdb-check: [chars] : "this" -//@ cdb-command: dx unique -//@ cdb-check:unique : Unique(0x[...]: (0x2a, 4321)) [Type: core::ptr::unique::Unique >] -//@ cdb-check: [] [Type: core::ptr::unique::Unique >] -//@ cdb-check: [0] : 0x2a [Type: unsigned __int64] -//@ cdb-check: [1] : 4321 [Type: int] - -#![feature(ptr_internals)] - use std::mem::ManuallyDrop; use std::pin::Pin; -use std::ptr::{NonNull, Unique}; +use std::ptr::NonNull; fn main() { let nonnull: NonNull<_> = (&12u32).into(); @@ -41,9 +33,7 @@ fn main() { let mut s = "this".to_string(); let pin = Pin::new(&mut s); - let unique: Unique<_> = (&mut (42u64, 4321i32)).into(); - zzz(); // #break } -fn zzz() { } +fn zzz() {} diff --git a/tests/ui/consts/const-ptr-unique-rpass.rs b/tests/ui/consts/const-ptr-unique-rpass.rs deleted file mode 100644 index db319b6ab9229..0000000000000 --- a/tests/ui/consts/const-ptr-unique-rpass.rs +++ /dev/null @@ -1,16 +0,0 @@ -//@ run-pass - -#![feature(ptr_internals, test)] - -extern crate test; -use test::black_box as b; // prevent promotion of the argument and const-propagation of the result - -use std::ptr::Unique; - - -const PTR: *mut u32 = Unique::dangling().as_ptr(); - -pub fn main() { - // Be super-extra paranoid and cast the fn items to fn pointers before blackboxing them. - assert_eq!(PTR, b:: _>(Unique::::dangling)().as_ptr()); -} diff --git a/tests/ui/consts/const-ptr-unique.rs b/tests/ui/consts/const-ptr-unique.rs deleted file mode 100644 index 252c5d1a9cda5..0000000000000 --- a/tests/ui/consts/const-ptr-unique.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![feature(ptr_internals)] - -use std::ptr::Unique; - -fn main() { - let mut i: u32 = 10; - let unique = Unique::new(&mut i).unwrap(); - let x: &'static *mut u32 = &(unique.as_ptr()); - //~^ ERROR temporary value dropped while borrowed -} diff --git a/tests/ui/consts/const-ptr-unique.stderr b/tests/ui/consts/const-ptr-unique.stderr deleted file mode 100644 index c3cc69830dfd8..0000000000000 --- a/tests/ui/consts/const-ptr-unique.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[E0716]: temporary value dropped while borrowed - --> $DIR/const-ptr-unique.rs:8:33 - | -LL | let x: &'static *mut u32 = &(unique.as_ptr()); - | ----------------- ^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use - | | - | type annotation requires that borrow lasts for `'static` -LL | -LL | } - | - temporary value is freed at the end of this statement - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/feature-gates/issue-49983-see-issue-0.rs b/tests/ui/feature-gates/issue-49983-see-issue-0.rs deleted file mode 100644 index eeb80d014b2d5..0000000000000 --- a/tests/ui/feature-gates/issue-49983-see-issue-0.rs +++ /dev/null @@ -1,6 +0,0 @@ -extern crate core; - -// error should not say "(see issue #0)" -#[allow(unused_imports)] use core::ptr::Unique; //~ ERROR use of unstable library feature - -fn main() {} diff --git a/tests/ui/feature-gates/issue-49983-see-issue-0.stderr b/tests/ui/feature-gates/issue-49983-see-issue-0.stderr deleted file mode 100644 index 29a2845852e44..0000000000000 --- a/tests/ui/feature-gates/issue-49983-see-issue-0.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[E0658]: use of unstable library feature `ptr_internals`: use `NonNull` instead and consider `PhantomData` (if you also use `#[may_dangle]`), `Send`, and/or `Sync` - --> $DIR/issue-49983-see-issue-0.rs:4:30 - | -LL | #[allow(unused_imports)] use core::ptr::Unique; - | ^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(ptr_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 1 previous error - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs new file mode 100644 index 0000000000000..c2d67e02ed520 --- /dev/null +++ b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs @@ -0,0 +1,14 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! An unresolved dereference must not treat a raw pointer as an overloaded `Deref`. + +fn make() -> T { + loop {} +} + +fn main() { + let pointer = make(); + //~^ ERROR type annotations needed + let value = unsafe { *pointer }; + let _: *const u8 = pointer; + let _: u8 = value; +} diff --git a/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr new file mode 100644 index 0000000000000..f6a2a772ffe54 --- /dev/null +++ b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr @@ -0,0 +1,17 @@ +error[E0282]: type annotations needed + --> $DIR/unary-deref-late-raw-pointer-inference-issue-106138.rs:9:9 + | +LL | let pointer = make(); + | ^^^^^^^ +LL | +LL | let value = unsafe { *pointer }; + | -------- type must be known at this point + | +help: consider giving `pointer` an explicit type + | +LL | let pointer: /* Type */ = make(); + | ++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. diff --git a/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs b/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs new file mode 100644 index 0000000000000..9f2ad98e5d6b4 --- /dev/null +++ b/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs @@ -0,0 +1,27 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/26830. +//! Unary negation should allow later constraints to determine its operand type. + +//@ check-pass + +fn make() -> T { + loop {} +} + +fn constrained_by_value() { + let input = make(); + let output = -input; + let _: i32 = input; + let _: i32 = output; +} + +fn constrained_by_borrow() { + let input = make(); + let output = -input; + let _: &i32 = &input; + let _: i32 = output; +} + +fn main() { + constrained_by_value(); + constrained_by_borrow(); +} diff --git a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs new file mode 100644 index 0000000000000..b30535115602e --- /dev/null +++ b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs @@ -0,0 +1,42 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! From comment https://github.com/rust-lang/rust/pull/107567#discussion_r1093589448 +//! Unary operator inference must not assume that the associated output type equals `Self`. + +#[derive(Copy, Clone, Default)] +struct A; + +struct B; + +impl std::ops::Not for A { + type Output = B; + + fn not(self) -> B { + B + } +} + +#[derive(Copy, Clone)] +struct NoNot; + +fn make() -> T { + loop {} +} + +fn resolved_without_impl() { + let value = make(); + let _ = !value; + //~^ ERROR the trait bound `NoNot: Not` is not satisfied + let _: NoNot = value; +} + +fn unconstrained() { + let value = make(); + //~^ ERROR type annotations needed + let _ = !value; +} + +fn main() { + let x = Default::default(); + //~^ ERROR cannot call associated function on trait + let _: A = !x; +} diff --git a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr new file mode 100644 index 0000000000000..30318239390e5 --- /dev/null +++ b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr @@ -0,0 +1,43 @@ +error[E0277]: the trait bound `NoNot: Not` is not satisfied + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:27:13 + | +LL | let _ = !value; + | ^^^^^^ unsatisfied trait bound + | +help: the trait `Not` is not implemented for `NoNot` + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:19:1 + | +LL | struct NoNot; + | ^^^^^^^^^^^^ + +error[E0284]: type annotations needed + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:33:9 + | +LL | let value = make(); + | ^^^^^ +LL | +LL | let _ = !value; + | ------ type must be known at this point + | + = note: cannot satisfy `<_ as Not>::Output == _` + = note: the type must also implement `Not` +help: consider giving `value` an explicit type + | +LL | let value: /* Type */ = make(); + | ++++++++++++ + +error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:39:13 + | +LL | let x = Default::default(); + | ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait + | +help: use a fully-qualified path to a specific available implementation + | +LL | let x = ::default(); + | +++++++++++++++++++ + + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0284, E0790. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs b/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs new file mode 100644 index 0000000000000..ca1b5eb66e550 --- /dev/null +++ b/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs @@ -0,0 +1,65 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! Unary operators should allow their operand types to be inferred by later constraints. + +//@ check-pass + +use std::ops::Not; + +// Indexing is a place projection, so `a[i]` autoderefs `a` and yields a `bool`. +// `!` is applied to that value: as a value operator it does not autoderef its +// operand itself, unlike the place projection `a[i]`. +fn not_index(x: &Vec) { + let closure = |i, a: &Vec| !a[i]; + let _ = closure(0, x); +} + +// Likewise, the `-` is applied to the `i32` produced by `a[i]`. +fn neg_index(x: &Vec) { + let closure = |i, a: &Vec| -a[i]; + let _ = closure(0, x); +} + +// Since the unary operators do not autoderef, these use `impl Not for &u32` and +// `impl Neg for &i32` rather than dereferencing to `u32`/`i32` first. +fn not_reference(x: &u32) -> u32 { + !x +} + +fn neg_reference(x: &i32) -> i32 { + -x +} + +#[derive(Copy, Clone, Default)] +struct Input; + +struct Output; + +impl Not for Input { + type Output = Output; + + fn not(self) -> Self::Output { + Output + } +} + +fn output_differs_from_operand() { + let input = Default::default(); + let output = !input; + let _: Input = input; + let _: Output = output; +} + +fn output_expectation_differs_from_operand() { + let input = Default::default(); + let _: Output = !input; + let _: Input = input; +} + +fn main() { + not_index(&vec![true]); + neg_index(&vec![1]); + let _ = not_reference(&1); + let _ = neg_reference(&1); + output_differs_from_operand(); + output_expectation_differs_from_operand(); +} diff --git a/tests/ui/inference/unary-op-no-autoderef.rs b/tests/ui/inference/unary-op-no-autoderef.rs new file mode 100644 index 0000000000000..9939dd287480f --- /dev/null +++ b/tests/ui/inference/unary-op-no-autoderef.rs @@ -0,0 +1,60 @@ +use std::ops::{Deref, Index, Neg, Not}; + +struct Inner { + value: u32, +} + +// `Inner` implements everything that the operators and place projections look +// up, so the contrast below is only about whether `Wrapper` is autoderef'd. +impl Not for Inner { + type Output = Inner; + + fn not(self) -> Inner { + self + } +} + +impl Neg for Inner { + type Output = Inner; + + fn neg(self) -> Inner { + self + } +} + +impl Index for Inner { + type Output = u32; + + fn index(&self, _: usize) -> &u32 { + &self.value + } +} + +struct Wrapper(Inner); + +impl Deref for Wrapper { + type Target = Inner; + + fn deref(&self) -> &Inner { + &self.0 + } +} + +fn main() { + let wrapper = Wrapper(Inner { value: 0 }); + + // Field access and indexing are place projections, so they autoderef + // `Wrapper` to `Inner` and both of these compile. + let _ = wrapper.value; + let _ = wrapper[0]; + + // `!` and `-` are value operators instead: they look up `Not` and `Neg` on + // the operand type itself and do not autoderef it. `Wrapper` implements + // neither trait, so both are rejected even though `Inner` does implement + // them and `Wrapper` derefs to `Inner`. + let _ = !wrapper; + //~^ ERROR cannot apply unary operator `!` to type `Wrapper` + + let _ = -wrapper; + //~^ ERROR cannot apply unary operator `-` to type `Wrapper` +} diff --git a/tests/ui/inference/unary-op-no-autoderef.stderr b/tests/ui/inference/unary-op-no-autoderef.stderr new file mode 100644 index 0000000000000..f77cdc02a2ae1 --- /dev/null +++ b/tests/ui/inference/unary-op-no-autoderef.stderr @@ -0,0 +1,31 @@ +error[E0600]: cannot apply unary operator `!` to type `Wrapper` + --> $DIR/unary-op-no-autoderef.rs:55:13 + | +LL | let _ = !wrapper; + | ^^^^^^^^ cannot apply unary operator `!` + | +note: an implementation of `Not` might be missing for `Wrapper` + --> $DIR/unary-op-no-autoderef.rs:33:1 + | +LL | struct Wrapper(Inner); + | ^^^^^^^^^^^^^^ must implement `Not` +note: the trait `Not` must be implemented + --> $SRC_DIR/core/src/ops/bit.rs:LL:COL + +error[E0600]: cannot apply unary operator `-` to type `Wrapper` + --> $DIR/unary-op-no-autoderef.rs:58:13 + | +LL | let _ = -wrapper; + | ^^^^^^^^ cannot apply unary operator `-` + | +note: an implementation of `Neg` might be missing for `Wrapper` + --> $DIR/unary-op-no-autoderef.rs:33:1 + | +LL | struct Wrapper(Inner); + | ^^^^^^^^^^^^^^ must implement `Neg` +note: the trait `Neg` must be implemented + --> $SRC_DIR/core/src/ops/arith.rs:LL:COL + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0600`. diff --git a/tests/ui/lint/improper-ctypes/lint-enum.rs b/tests/ui/lint/improper-ctypes/lint-enum.rs index f900f998d06cb..cfc9842bd6e15 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.rs +++ b/tests/ui/lint/improper-ctypes/lint-enum.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] #![deny(improper_ctypes)] -#![feature(ptr_internals)] #![feature(transparent_unions)] use std::num; @@ -90,7 +89,6 @@ extern "C" { fn option_ref(x: Option<&'static u8>); fn option_fn(x: Option); fn option_nonnull(x: Option>); - fn option_unique(x: Option>); fn option_nonzero_u8(x: Option>); fn option_nonzero_u16(x: Option>); fn option_nonzero_u32(x: Option>); @@ -113,7 +111,6 @@ extern "C" { fn result_ref_t(x: Result<&'static u8, ()>); fn result_fn_t(x: Result); fn result_nonnull_t(x: Result, ()>); - fn result_unique_t(x: Result, ()>); fn result_nonzero_u8_t(x: Result, ()>); fn result_nonzero_u16_t(x: Result, ()>); fn result_nonzero_u32_t(x: Result, ()>); @@ -149,7 +146,6 @@ extern "C" { fn result_ref_e(x: Result<(), &'static u8>); fn result_fn_e(x: Result<(), extern "C" fn()>); fn result_nonnull_e(x: Result<(), std::ptr::NonNull>); - fn result_unique_e(x: Result<(), std::ptr::Unique>); fn result_nonzero_u8_e(x: Result<(), num::NonZero>); fn result_nonzero_u16_e(x: Result<(), num::NonZero>); fn result_nonzero_u32_e(x: Result<(), num::NonZero>); diff --git a/tests/ui/lint/improper-ctypes/lint-enum.stderr b/tests/ui/lint/improper-ctypes/lint-enum.stderr index 35d1dcb87fd82..3a41f577c86d3 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.stderr +++ b/tests/ui/lint/improper-ctypes/lint-enum.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `U`, which is not FFI-safe - --> $DIR/lint-enum.rs:82:14 + --> $DIR/lint-enum.rs:81:14 | LL | fn uf(x: U); | ^ not FFI-safe @@ -7,7 +7,7 @@ LL | fn uf(x: U); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:9:1 + --> $DIR/lint-enum.rs:8:1 | LL | enum U { | ^^^^^^ @@ -18,7 +18,7 @@ LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `B`, which is not FFI-safe - --> $DIR/lint-enum.rs:83:14 + --> $DIR/lint-enum.rs:82:14 | LL | fn bf(x: B); | ^ not FFI-safe @@ -26,13 +26,13 @@ LL | fn bf(x: B); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:12:1 + --> $DIR/lint-enum.rs:11:1 | LL | enum B { | ^^^^^^ error: `extern` block uses type `T`, which is not FFI-safe - --> $DIR/lint-enum.rs:84:14 + --> $DIR/lint-enum.rs:83:14 | LL | fn tf(x: T); | ^ not FFI-safe @@ -40,13 +40,13 @@ LL | fn tf(x: T); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:16:1 + --> $DIR/lint-enum.rs:15:1 | LL | enum T { | ^^^^^^ error: `extern` block uses type `Option>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:108:36 + --> $DIR/lint-enum.rs:106:36 | LL | fn option_transparent_union(x: Option>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -55,7 +55,7 @@ LL | fn option_transparent_union(x: Option = note: enum has no representation hint error: `extern` block uses type `Option>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:110:28 + --> $DIR/lint-enum.rs:108:28 | LL | fn option_repr_rust(x: Option>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -64,7 +64,7 @@ LL | fn option_repr_rust(x: Option>>); = note: enum has no representation hint error: `extern` block uses type `Option`, which is not FFI-safe - --> $DIR/lint-enum.rs:111:21 + --> $DIR/lint-enum.rs:109:21 | LL | fn option_u8(x: Option); | ^^^^^^^^^^ not FFI-safe @@ -73,7 +73,7 @@ LL | fn option_u8(x: Option); = note: enum has no representation hint error: `extern` block uses type `Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:131:38 + --> $DIR/lint-enum.rs:128:38 | LL | fn result_transparent_union_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -82,7 +82,7 @@ LL | fn result_transparent_union_t(x: Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:133:30 + --> $DIR/lint-enum.rs:130:30 | LL | fn result_repr_rust_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -91,7 +91,7 @@ LL | fn result_repr_rust_t(x: Result>, ()>); = note: enum has no representation hint error: `extern` block uses type `Result, U>`, which is not FFI-safe - --> $DIR/lint-enum.rs:137:51 + --> $DIR/lint-enum.rs:134:51 | LL | fn result_1zst_exhaustive_single_variant_t(x: Result, U>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -100,7 +100,7 @@ LL | fn result_1zst_exhaustive_single_variant_t(x: Result, = note: enum has no representation hint error: `extern` block uses type `Result, B>`, which is not FFI-safe - --> $DIR/lint-enum.rs:139:53 + --> $DIR/lint-enum.rs:136:53 | LL | fn result_1zst_exhaustive_multiple_variant_t(x: Result, B>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -109,7 +109,7 @@ LL | fn result_1zst_exhaustive_multiple_variant_t(x: Result = note: enum has no representation hint error: `extern` block uses type `Result, NonExhaustive>`, which is not FFI-safe - --> $DIR/lint-enum.rs:141:51 + --> $DIR/lint-enum.rs:138:51 | LL | fn result_1zst_non_exhaustive_no_variant_t(x: Result, NonExhaustive>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -118,7 +118,7 @@ LL | fn result_1zst_non_exhaustive_no_variant_t(x: Result, = note: enum has no representation hint error: `extern` block uses type `Result, Field>`, which is not FFI-safe - --> $DIR/lint-enum.rs:144:49 + --> $DIR/lint-enum.rs:141:49 | LL | fn result_1zst_exhaustive_single_field_t(x: Result, Field>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -127,7 +127,7 @@ LL | fn result_1zst_exhaustive_single_field_t(x: Result, Fi = note: enum has no representation hint error: `extern` block uses type `Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:146:30 + --> $DIR/lint-enum.rs:143:30 | LL | fn result_cascading_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -136,7 +136,7 @@ LL | fn result_cascading_t(x: Result>, ()>); = note: enum has no representation hint error: `extern` block uses type `Result<(), TransparentUnion>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:167:38 + --> $DIR/lint-enum.rs:163:38 | LL | fn result_transparent_union_e(x: Result<(), TransparentUnion>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -145,7 +145,7 @@ LL | fn result_transparent_union_e(x: Result<(), TransparentUnion>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:169:30 + --> $DIR/lint-enum.rs:165:30 | LL | fn result_repr_rust_e(x: Result<(), Rust>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -154,7 +154,7 @@ LL | fn result_repr_rust_e(x: Result<(), Rust>>); = note: enum has no representation hint error: `extern` block uses type `Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:173:51 + --> $DIR/lint-enum.rs:169:51 | LL | fn result_1zst_exhaustive_single_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -163,7 +163,7 @@ LL | fn result_1zst_exhaustive_single_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:175:53 + --> $DIR/lint-enum.rs:171:53 | LL | fn result_1zst_exhaustive_multiple_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -172,7 +172,7 @@ LL | fn result_1zst_exhaustive_multiple_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:177:51 + --> $DIR/lint-enum.rs:173:51 | LL | fn result_1zst_non_exhaustive_no_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -181,7 +181,7 @@ LL | fn result_1zst_non_exhaustive_no_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:180:49 + --> $DIR/lint-enum.rs:176:49 | LL | fn result_1zst_exhaustive_single_field_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -190,7 +190,7 @@ LL | fn result_1zst_exhaustive_single_field_e(x: Result>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:182:30 + --> $DIR/lint-enum.rs:178:30 | LL | fn result_cascading_e(x: Result<(), Result<(), num::NonZero>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -199,7 +199,7 @@ LL | fn result_cascading_e(x: Result<(), Result<(), num::NonZero>>); = note: enum has no representation hint error: `extern` block uses type `Result<(), ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:184:27 + --> $DIR/lint-enum.rs:180:27 | LL | fn result_unit_t_e(x: Result<(), ()>); | ^^^^^^^^^^^^^^ not FFI-safe diff --git a/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs b/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs index 9b8092526b950..b01aa16825286 100644 --- a/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs +++ b/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs @@ -24,7 +24,6 @@ which also will affect its size if the enum has a non-zero size. #![allow(dead_code)] #![deny(improper_ctypes)] -#![feature(ptr_internals)] use std::num; @@ -44,7 +43,6 @@ extern "C" { fn result_ref_t(x: Result<&'static u8, ()>); fn result_fn_t(x: Result); fn result_nonnull_t(x: Result, ()>); - fn result_unique_t(x: Result, ()>); fn result_nonzero_u8_t(x: Result, ()>); fn result_nonzero_u16_t(x: Result, ()>); fn result_nonzero_u32_t(x: Result, ()>); @@ -64,7 +62,6 @@ extern "C" { fn result_ref_e(x: Result<(), &'static u8>); fn result_fn_e(x: Result<(), extern "C" fn()>); fn result_nonnull_e(x: Result<(), std::ptr::NonNull>); - fn result_unique_e(x: Result<(), std::ptr::Unique>); fn result_nonzero_u8_e(x: Result<(), num::NonZero>); fn result_nonzero_u16_e(x: Result<(), num::NonZero>); fn result_nonzero_u32_e(x: Result<(), num::NonZero>); @@ -92,7 +89,6 @@ extern "C" { fn either_ref_t(x: Either<&'static u8, ()>); fn either_fn_t(x: Either); fn either_nonnull_t(x: Either, ()>); - fn either_unique_t(x: Either, ()>); fn either_nonzero_u8_t(x: Either, ()>); fn either_nonzero_u16_t(x: Either, ()>); fn either_nonzero_u32_t(x: Either, ()>); @@ -112,7 +108,6 @@ extern "C" { fn either_ref_e(x: Either<(), &'static u8>); fn either_fn_e(x: Either<(), extern "C" fn()>); fn either_nonnull_e(x: Either<(), std::ptr::NonNull>); - fn either_unique_e(x: Either<(), std::ptr::Unique>); fn either_nonzero_u8_e(x: Either<(), num::NonZero>); fn either_nonzero_u16_e(x: Either<(), num::NonZero>); fn either_nonzero_u32_e(x: Either<(), num::NonZero>);