Auto merge of #135420 - GuillaumeGomez:rollup-93vepka, r=GuillaumeGomez
Rollup of 7 pull requests Successful merges: - #135348 (rustdoc-json: Include items in stripped modules in `Crate::paths`.) - #135365 (Update the explanation for why we use box_new in vec!) - #135383 (De-abstract tagged ptr and make it covariant) - #135401 (Remove some empty expected files to fix blessing) - #135406 (Update unstable lint docs to include required feature attributes) - #135407 (Deny various clippy lints) - #135411 (run_make_support: add `#![warn(unreachable_pub)]`) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
e7ad3ae331
25 changed files with 501 additions and 1030 deletions
|
@ -72,7 +72,7 @@ impl_dyn_send!(
|
|||
[Vec<T, A> where T: DynSend, A: std::alloc::Allocator + DynSend]
|
||||
[Box<T, A> where T: ?Sized + DynSend, A: std::alloc::Allocator + DynSend]
|
||||
[crate::sync::RwLock<T> where T: DynSend]
|
||||
[crate::tagged_ptr::CopyTaggedPtr<P, T, CP> where P: Send + crate::tagged_ptr::Pointer, T: Send + crate::tagged_ptr::Tag, const CP: bool]
|
||||
[crate::tagged_ptr::TaggedRef<'a, P, T> where 'a, P: Sync, T: Send + crate::tagged_ptr::Tag]
|
||||
[rustc_arena::TypedArena<T> where T: DynSend]
|
||||
[indexmap::IndexSet<V, S> where V: DynSend, S: DynSend]
|
||||
[indexmap::IndexMap<K, V, S> where K: DynSend, V: DynSend, S: DynSend]
|
||||
|
@ -148,7 +148,7 @@ impl_dyn_sync!(
|
|||
[crate::sync::RwLock<T> where T: DynSend + DynSync]
|
||||
[crate::sync::WorkerLocal<T> where T: DynSend]
|
||||
[crate::intern::Interned<'a, T> where 'a, T: DynSync]
|
||||
[crate::tagged_ptr::CopyTaggedPtr<P, T, CP> where P: Sync + crate::tagged_ptr::Pointer, T: Sync + crate::tagged_ptr::Tag, const CP: bool]
|
||||
[crate::tagged_ptr::TaggedRef<'a, P, T> where 'a, P: Sync, T: Sync + crate::tagged_ptr::Tag]
|
||||
[parking_lot::lock_api::Mutex<R, T> where R: DynSync, T: ?Sized + DynSend]
|
||||
[parking_lot::lock_api::RwLock<R, T> where R: DynSync, T: ?Sized + DynSend + DynSync]
|
||||
[indexmap::IndexSet<V, S> where V: DynSync, S: DynSync]
|
||||
|
|
|
@ -1,116 +1,26 @@
|
|||
//! This module implements tagged pointers.
|
||||
//! This module implements tagged pointers. In order to utilize the pointer
|
||||
//! packing, you must have a tag type implementing the [`Tag`] trait.
|
||||
//!
|
||||
//! In order to utilize the pointer packing, you must have two types: a pointer,
|
||||
//! and a tag.
|
||||
//!
|
||||
//! The pointer must implement the [`Pointer`] trait, with the primary
|
||||
//! requirement being convertible to and from a raw pointer. Note that the
|
||||
//! pointer must be dereferenceable, so raw pointers generally cannot implement
|
||||
//! the [`Pointer`] trait. This implies that the pointer must also be non-null.
|
||||
//!
|
||||
//! Many common pointer types already implement the [`Pointer`] trait.
|
||||
//!
|
||||
//! The tag must implement the [`Tag`] trait.
|
||||
//!
|
||||
//! We assert that the tag and the [`Pointer`] types are compatible at compile
|
||||
//! We assert that the tag and the reference type is compatible at compile
|
||||
//! time.
|
||||
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use std::num::NonZero;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::aligned::Aligned;
|
||||
use crate::stable_hasher::{HashStable, StableHasher};
|
||||
|
||||
mod copy;
|
||||
mod drop;
|
||||
mod impl_tag;
|
||||
|
||||
pub use copy::CopyTaggedPtr;
|
||||
pub use drop::TaggedPtr;
|
||||
|
||||
/// This describes the pointer type encapsulated by [`TaggedPtr`] and
|
||||
/// [`CopyTaggedPtr`].
|
||||
/// This describes tags that the [`TaggedRef`] struct can hold.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The pointer returned from [`into_ptr`] must be a [valid], pointer to
|
||||
/// [`<Self as Deref>::Target`].
|
||||
///
|
||||
/// Note that if `Self` implements [`DerefMut`] the pointer returned from
|
||||
/// [`into_ptr`] must be valid for writes (and thus calling [`NonNull::as_mut`]
|
||||
/// on it must be safe).
|
||||
///
|
||||
/// The [`BITS`] constant must be correct. [`BITS`] least-significant bits,
|
||||
/// must be zero on all pointers returned from [`into_ptr`].
|
||||
///
|
||||
/// For example, if the alignment of [`Self::Target`] is 2, then `BITS` should be 1.
|
||||
///
|
||||
/// [`BITS`]: Pointer::BITS
|
||||
/// [`into_ptr`]: Pointer::into_ptr
|
||||
/// [valid]: std::ptr#safety
|
||||
/// [`<Self as Deref>::Target`]: Deref::Target
|
||||
/// [`Self::Target`]: Deref::Target
|
||||
/// [`DerefMut`]: std::ops::DerefMut
|
||||
pub unsafe trait Pointer: Deref {
|
||||
/// Number of unused (always zero) **least-significant bits** in this
|
||||
/// pointer, usually related to the pointees alignment.
|
||||
///
|
||||
/// For example if [`BITS`] = `2`, then given `ptr = Self::into_ptr(..)`,
|
||||
/// `ptr.addr() & 0b11 == 0` must be true.
|
||||
///
|
||||
/// Most likely the value you want to use here is the following, unless
|
||||
/// your [`Self::Target`] type is unsized (e.g., `ty::List<T>` in rustc)
|
||||
/// or your pointer is over/under aligned, in which case you'll need to
|
||||
/// manually figure out what the right type to pass to [`bits_for`] is, or
|
||||
/// what the value to set here.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::ops::Deref;
|
||||
/// # use rustc_data_structures::tagged_ptr::bits_for;
|
||||
/// # struct T;
|
||||
/// # impl Deref for T { type Target = u8; fn deref(&self) -> &u8 { &0 } }
|
||||
/// # impl T {
|
||||
/// const BITS: u32 = bits_for::<<Self as Deref>::Target>();
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`BITS`]: Pointer::BITS
|
||||
/// [`Self::Target`]: Deref::Target
|
||||
const BITS: u32;
|
||||
|
||||
/// Turns this pointer into a raw, non-null pointer.
|
||||
///
|
||||
/// The inverse of this function is [`from_ptr`].
|
||||
///
|
||||
/// This function guarantees that the least-significant [`Self::BITS`] bits
|
||||
/// are zero.
|
||||
///
|
||||
/// [`from_ptr`]: Pointer::from_ptr
|
||||
/// [`Self::BITS`]: Pointer::BITS
|
||||
fn into_ptr(self) -> NonNull<Self::Target>;
|
||||
|
||||
/// Re-creates the original pointer, from a raw pointer returned by [`into_ptr`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The passed `ptr` must be returned from [`into_ptr`].
|
||||
///
|
||||
/// This acts as [`ptr::read::<Self>()`] semantically, it should not be called more than
|
||||
/// once on non-[`Copy`] `Pointer`s.
|
||||
///
|
||||
/// [`into_ptr`]: Pointer::into_ptr
|
||||
/// [`ptr::read::<Self>()`]: std::ptr::read
|
||||
unsafe fn from_ptr(ptr: NonNull<Self::Target>) -> Self;
|
||||
}
|
||||
|
||||
/// This describes tags that the [`TaggedPtr`] struct can hold.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`BITS`] constant must be correct.
|
||||
///
|
||||
/// No more than [`BITS`] least-significant bits may be set in the returned usize.
|
||||
/// - The [`BITS`] constant must be correct.
|
||||
/// - No more than [`BITS`] least-significant bits may be set in the returned usize.
|
||||
/// - [`Eq`] and [`Hash`] must be implementable with the returned `usize` from `into_usize`.
|
||||
///
|
||||
/// [`BITS`]: Tag::BITS
|
||||
pub unsafe trait Tag: Copy {
|
||||
|
@ -166,118 +76,217 @@ pub const fn bits_for_tags(mut tags: &[usize]) -> u32 {
|
|||
bits
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Aligned> Pointer for Box<T> {
|
||||
const BITS: u32 = bits_for::<Self::Target>();
|
||||
|
||||
#[inline]
|
||||
fn into_ptr(self) -> NonNull<T> {
|
||||
// Safety: pointers from `Box::into_raw` are valid & non-null
|
||||
unsafe { NonNull::new_unchecked(Box::into_raw(self)) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_ptr(ptr: NonNull<T>) -> Self {
|
||||
// Safety: `ptr` comes from `into_ptr` which calls `Box::into_raw`
|
||||
unsafe { Box::from_raw(ptr.as_ptr()) }
|
||||
}
|
||||
/// A covariant [`Copy`] tagged borrow. This is essentially `{ pointer: &'a P, tag: T }` packed
|
||||
/// in a single reference.
|
||||
pub struct TaggedRef<'a, Pointee: Aligned + ?Sized, T: Tag> {
|
||||
/// This is semantically a pair of `pointer: &'a P` and `tag: T` fields,
|
||||
/// however we pack them in a single pointer, to save space.
|
||||
///
|
||||
/// We pack the tag into the **most**-significant bits of the pointer to
|
||||
/// ease retrieval of the value. A left shift is a multiplication and
|
||||
/// those are embeddable in instruction encoding, for example:
|
||||
///
|
||||
/// ```asm
|
||||
/// // (<https://godbolt.org/z/jqcYPWEr3>)
|
||||
/// example::shift_read3:
|
||||
/// mov eax, dword ptr [8*rdi]
|
||||
/// ret
|
||||
///
|
||||
/// example::mask_read3:
|
||||
/// and rdi, -8
|
||||
/// mov eax, dword ptr [rdi]
|
||||
/// ret
|
||||
/// ```
|
||||
///
|
||||
/// This is ASM outputted by rustc for reads of values behind tagged
|
||||
/// pointers for different approaches of tagging:
|
||||
/// - `shift_read3` uses `<< 3` (the tag is in the most-significant bits)
|
||||
/// - `mask_read3` uses `& !0b111` (the tag is in the least-significant bits)
|
||||
///
|
||||
/// The shift approach thus produces less instructions and is likely faster
|
||||
/// (see <https://godbolt.org/z/Y913sMdWb>).
|
||||
///
|
||||
/// Encoding diagram:
|
||||
/// ```text
|
||||
/// [ packed.addr ]
|
||||
/// [ tag ] [ pointer.addr >> T::BITS ] <-- usize::BITS - T::BITS bits
|
||||
/// ^
|
||||
/// |
|
||||
/// T::BITS bits
|
||||
/// ```
|
||||
///
|
||||
/// The tag can be retrieved by `packed.addr() >> T::BITS` and the pointer
|
||||
/// can be retrieved by `packed.map_addr(|addr| addr << T::BITS)`.
|
||||
packed: NonNull<Pointee>,
|
||||
tag_pointer_ghost: PhantomData<(&'a Pointee, T)>,
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Aligned> Pointer for Rc<T> {
|
||||
const BITS: u32 = bits_for::<Self::Target>();
|
||||
|
||||
impl<'a, P, T> TaggedRef<'a, P, T>
|
||||
where
|
||||
P: Aligned + ?Sized,
|
||||
T: Tag,
|
||||
{
|
||||
/// Tags `pointer` with `tag`.
|
||||
///
|
||||
/// [`TaggedRef`]: crate::tagged_ptr::TaggedRef
|
||||
#[inline]
|
||||
fn into_ptr(self) -> NonNull<T> {
|
||||
// Safety: pointers from `Rc::into_raw` are valid & non-null
|
||||
unsafe { NonNull::new_unchecked(Rc::into_raw(self).cast_mut()) }
|
||||
pub fn new(pointer: &'a P, tag: T) -> Self {
|
||||
Self { packed: Self::pack(NonNull::from(pointer), tag), tag_pointer_ghost: PhantomData }
|
||||
}
|
||||
|
||||
/// Retrieves the pointer.
|
||||
#[inline]
|
||||
unsafe fn from_ptr(ptr: NonNull<T>) -> Self {
|
||||
// Safety: `ptr` comes from `into_ptr` which calls `Rc::into_raw`
|
||||
unsafe { Rc::from_raw(ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Aligned> Pointer for Arc<T> {
|
||||
const BITS: u32 = bits_for::<Self::Target>();
|
||||
|
||||
#[inline]
|
||||
fn into_ptr(self) -> NonNull<T> {
|
||||
// Safety: pointers from `Arc::into_raw` are valid & non-null
|
||||
unsafe { NonNull::new_unchecked(Arc::into_raw(self).cast_mut()) }
|
||||
pub fn pointer(self) -> &'a P {
|
||||
// SAFETY: pointer_raw returns the original pointer
|
||||
unsafe { self.pointer_raw().as_ref() }
|
||||
}
|
||||
|
||||
/// Retrieves the tag.
|
||||
#[inline]
|
||||
unsafe fn from_ptr(ptr: NonNull<T>) -> Self {
|
||||
// Safety: `ptr` comes from `into_ptr` which calls `Arc::into_raw`
|
||||
unsafe { Arc::from_raw(ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
pub fn tag(&self) -> T {
|
||||
// Unpack the tag, according to the `self.packed` encoding scheme
|
||||
let tag = self.packed.addr().get() >> Self::TAG_BIT_SHIFT;
|
||||
|
||||
unsafe impl<'a, T: 'a + ?Sized + Aligned> Pointer for &'a T {
|
||||
const BITS: u32 = bits_for::<Self::Target>();
|
||||
|
||||
#[inline]
|
||||
fn into_ptr(self) -> NonNull<T> {
|
||||
NonNull::from(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_ptr(ptr: NonNull<T>) -> Self {
|
||||
// Safety:
|
||||
// `ptr` comes from `into_ptr` which gets the pointer from a reference
|
||||
unsafe { ptr.as_ref() }
|
||||
// The shift retrieves the original value from `T::into_usize`,
|
||||
// satisfying `T::from_usize`'s preconditions.
|
||||
unsafe { T::from_usize(tag) }
|
||||
}
|
||||
|
||||
/// Sets the tag to a new value.
|
||||
#[inline]
|
||||
pub fn set_tag(&mut self, tag: T) {
|
||||
self.packed = Self::pack(self.pointer_raw(), tag);
|
||||
}
|
||||
|
||||
const TAG_BIT_SHIFT: u32 = usize::BITS - T::BITS;
|
||||
const ASSERTION: () = { assert!(T::BITS <= bits_for::<P>()) };
|
||||
|
||||
/// Pack pointer `ptr` with a `tag`, according to `self.packed` encoding scheme.
|
||||
#[inline]
|
||||
fn pack(ptr: NonNull<P>, tag: T) -> NonNull<P> {
|
||||
// Trigger assert!
|
||||
let () = Self::ASSERTION;
|
||||
|
||||
let packed_tag = tag.into_usize() << Self::TAG_BIT_SHIFT;
|
||||
|
||||
ptr.map_addr(|addr| {
|
||||
// Safety:
|
||||
// - The pointer is `NonNull` => it's address is `NonZero<usize>`
|
||||
// - `P::BITS` least significant bits are always zero (`Pointer` contract)
|
||||
// - `T::BITS <= P::BITS` (from `Self::ASSERTION`)
|
||||
//
|
||||
// Thus `addr >> T::BITS` is guaranteed to be non-zero.
|
||||
//
|
||||
// `{non_zero} | packed_tag` can't make the value zero.
|
||||
|
||||
let packed = (addr.get() >> T::BITS) | packed_tag;
|
||||
unsafe { NonZero::new_unchecked(packed) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves the original raw pointer from `self.packed`.
|
||||
#[inline]
|
||||
pub(super) fn pointer_raw(&self) -> NonNull<P> {
|
||||
self.packed.map_addr(|addr| unsafe { NonZero::new_unchecked(addr.get() << T::BITS) })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a, T: 'a + ?Sized + Aligned> Pointer for &'a mut T {
|
||||
const BITS: u32 = bits_for::<Self::Target>();
|
||||
impl<P, T> Copy for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Aligned + ?Sized,
|
||||
T: Tag,
|
||||
{
|
||||
}
|
||||
|
||||
impl<P, T> Clone for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Aligned + ?Sized,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Deref for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Aligned + ?Sized,
|
||||
T: Tag,
|
||||
{
|
||||
type Target = P;
|
||||
|
||||
#[inline]
|
||||
fn into_ptr(self) -> NonNull<T> {
|
||||
NonNull::from(self)
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.pointer()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> fmt::Debug for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Aligned + fmt::Debug + ?Sized,
|
||||
T: Tag + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TaggedRef")
|
||||
.field("pointer", &self.pointer())
|
||||
.field("tag", &self.tag())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> PartialEq for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Aligned + ?Sized,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
unsafe fn from_ptr(mut ptr: NonNull<T>) -> Self {
|
||||
// Safety:
|
||||
// `ptr` comes from `into_ptr` which gets the pointer from a reference
|
||||
unsafe { ptr.as_mut() }
|
||||
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.packed == other.packed
|
||||
}
|
||||
}
|
||||
|
||||
/// A tag type used in [`CopyTaggedPtr`] and [`TaggedPtr`] tests.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg(test)]
|
||||
enum Tag2 {
|
||||
B00 = 0b00,
|
||||
B01 = 0b01,
|
||||
B10 = 0b10,
|
||||
B11 = 0b11,
|
||||
impl<P, T: Tag> Eq for TaggedRef<'_, P, T> {}
|
||||
|
||||
impl<P, T: Tag> Hash for TaggedRef<'_, P, T> {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.packed.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, T, HCX> HashStable<HCX> for TaggedRef<'a, P, T>
|
||||
where
|
||||
P: HashStable<HCX> + Aligned + ?Sized,
|
||||
T: Tag + HashStable<HCX>,
|
||||
{
|
||||
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
|
||||
self.pointer().hash_stable(hcx, hasher);
|
||||
self.tag().hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// `TaggedRef<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
|
||||
// it's ok to implement `Sync` as long as `P: Sync, T: Sync`
|
||||
unsafe impl<P, T> Sync for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Sync + Aligned + ?Sized,
|
||||
T: Sync + Tag,
|
||||
{
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// `TaggedRef<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
|
||||
// it's ok to implement `Send` as long as `P: Send, T: Send`
|
||||
unsafe impl<P, T> Send for TaggedRef<'_, P, T>
|
||||
where
|
||||
P: Sync + Aligned + ?Sized,
|
||||
T: Send + Tag,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
unsafe impl Tag for Tag2 {
|
||||
const BITS: u32 = 2;
|
||||
|
||||
fn into_usize(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
unsafe fn from_usize(tag: usize) -> Self {
|
||||
match tag {
|
||||
0b00 => Tag2::B00,
|
||||
0b01 => Tag2::B01,
|
||||
0b10 => Tag2::B10,
|
||||
0b11 => Tag2::B11,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<HCX> crate::stable_hasher::HashStable<HCX> for Tag2 {
|
||||
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut crate::stable_hasher::StableHasher) {
|
||||
(*self as u8).hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZero;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::{Pointer, Tag};
|
||||
use crate::stable_hasher::{HashStable, StableHasher};
|
||||
|
||||
/// A [`Copy`] tagged pointer.
|
||||
///
|
||||
/// This is essentially `{ pointer: P, tag: T }` packed in a single pointer.
|
||||
///
|
||||
/// You should use this instead of the [`TaggedPtr`] type in all cases where
|
||||
/// `P` implements [`Copy`].
|
||||
///
|
||||
/// If `COMPARE_PACKED` is true, then the pointers will be compared and hashed without
|
||||
/// unpacking. Otherwise we don't implement [`PartialEq`], [`Eq`] and [`Hash`];
|
||||
/// if you want that, wrap the [`CopyTaggedPtr`].
|
||||
///
|
||||
/// [`TaggedPtr`]: crate::tagged_ptr::TaggedPtr
|
||||
pub struct CopyTaggedPtr<P, T, const COMPARE_PACKED: bool>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
/// This is semantically a pair of `pointer: P` and `tag: T` fields,
|
||||
/// however we pack them in a single pointer, to save space.
|
||||
///
|
||||
/// We pack the tag into the **most**-significant bits of the pointer to
|
||||
/// ease retrieval of the value. A left shift is a multiplication and
|
||||
/// those are embeddable in instruction encoding, for example:
|
||||
///
|
||||
/// ```asm
|
||||
/// // (<https://godbolt.org/z/jqcYPWEr3>)
|
||||
/// example::shift_read3:
|
||||
/// mov eax, dword ptr [8*rdi]
|
||||
/// ret
|
||||
///
|
||||
/// example::mask_read3:
|
||||
/// and rdi, -8
|
||||
/// mov eax, dword ptr [rdi]
|
||||
/// ret
|
||||
/// ```
|
||||
///
|
||||
/// This is ASM outputted by rustc for reads of values behind tagged
|
||||
/// pointers for different approaches of tagging:
|
||||
/// - `shift_read3` uses `<< 3` (the tag is in the most-significant bits)
|
||||
/// - `mask_read3` uses `& !0b111` (the tag is in the least-significant bits)
|
||||
///
|
||||
/// The shift approach thus produces less instructions and is likely faster
|
||||
/// (see <https://godbolt.org/z/Y913sMdWb>).
|
||||
///
|
||||
/// Encoding diagram:
|
||||
/// ```text
|
||||
/// [ packed.addr ]
|
||||
/// [ tag ] [ pointer.addr >> T::BITS ] <-- usize::BITS - T::BITS bits
|
||||
/// ^
|
||||
/// |
|
||||
/// T::BITS bits
|
||||
/// ```
|
||||
///
|
||||
/// The tag can be retrieved by `packed.addr() >> T::BITS` and the pointer
|
||||
/// can be retrieved by `packed.map_addr(|addr| addr << T::BITS)`.
|
||||
packed: NonNull<P::Target>,
|
||||
tag_ghost: PhantomData<T>,
|
||||
}
|
||||
|
||||
// Note that even though `CopyTaggedPtr` is only really expected to work with
|
||||
// `P: Copy`, can't add `P: Copy` bound, because `CopyTaggedPtr` is used in the
|
||||
// `TaggedPtr`'s implementation.
|
||||
impl<P, T, const CP: bool> CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
/// Tags `pointer` with `tag`.
|
||||
///
|
||||
/// Note that this leaks `pointer`: it won't be dropped when
|
||||
/// `CopyTaggedPtr` is dropped. If you have a pointer with a significant
|
||||
/// drop, use [`TaggedPtr`] instead.
|
||||
///
|
||||
/// [`TaggedPtr`]: crate::tagged_ptr::TaggedPtr
|
||||
#[inline]
|
||||
pub fn new(pointer: P, tag: T) -> Self {
|
||||
Self { packed: Self::pack(P::into_ptr(pointer), tag), tag_ghost: PhantomData }
|
||||
}
|
||||
|
||||
/// Retrieves the pointer.
|
||||
#[inline]
|
||||
pub fn pointer(self) -> P
|
||||
where
|
||||
P: Copy,
|
||||
{
|
||||
// SAFETY: pointer_raw returns the original pointer
|
||||
//
|
||||
// Note that this isn't going to double-drop or anything because we have
|
||||
// P: Copy
|
||||
unsafe { P::from_ptr(self.pointer_raw()) }
|
||||
}
|
||||
|
||||
/// Retrieves the tag.
|
||||
#[inline]
|
||||
pub fn tag(&self) -> T {
|
||||
// Unpack the tag, according to the `self.packed` encoding scheme
|
||||
let tag = self.packed.addr().get() >> Self::TAG_BIT_SHIFT;
|
||||
|
||||
// Safety:
|
||||
// The shift retrieves the original value from `T::into_usize`,
|
||||
// satisfying `T::from_usize`'s preconditions.
|
||||
unsafe { T::from_usize(tag) }
|
||||
}
|
||||
|
||||
/// Sets the tag to a new value.
|
||||
#[inline]
|
||||
pub fn set_tag(&mut self, tag: T) {
|
||||
self.packed = Self::pack(self.pointer_raw(), tag);
|
||||
}
|
||||
|
||||
const TAG_BIT_SHIFT: u32 = usize::BITS - T::BITS;
|
||||
const ASSERTION: () = { assert!(T::BITS <= P::BITS) };
|
||||
|
||||
/// Pack pointer `ptr` that comes from [`P::into_ptr`] with a `tag`,
|
||||
/// according to `self.packed` encoding scheme.
|
||||
///
|
||||
/// [`P::into_ptr`]: Pointer::into_ptr
|
||||
#[inline]
|
||||
fn pack(ptr: NonNull<P::Target>, tag: T) -> NonNull<P::Target> {
|
||||
// Trigger assert!
|
||||
let () = Self::ASSERTION;
|
||||
|
||||
let packed_tag = tag.into_usize() << Self::TAG_BIT_SHIFT;
|
||||
|
||||
ptr.map_addr(|addr| {
|
||||
// Safety:
|
||||
// - The pointer is `NonNull` => it's address is `NonZero<usize>`
|
||||
// - `P::BITS` least significant bits are always zero (`Pointer` contract)
|
||||
// - `T::BITS <= P::BITS` (from `Self::ASSERTION`)
|
||||
//
|
||||
// Thus `addr >> T::BITS` is guaranteed to be non-zero.
|
||||
//
|
||||
// `{non_zero} | packed_tag` can't make the value zero.
|
||||
|
||||
let packed = (addr.get() >> T::BITS) | packed_tag;
|
||||
unsafe { NonZero::new_unchecked(packed) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves the original raw pointer from `self.packed`.
|
||||
#[inline]
|
||||
pub(super) fn pointer_raw(&self) -> NonNull<P::Target> {
|
||||
self.packed.map_addr(|addr| unsafe { NonZero::new_unchecked(addr.get() << T::BITS) })
|
||||
}
|
||||
|
||||
/// This provides a reference to the `P` pointer itself, rather than the
|
||||
/// `Deref::Target`. It is used for cases where we want to call methods
|
||||
/// that may be implement differently for the Pointer than the Pointee
|
||||
/// (e.g., `Rc::clone` vs cloning the inner value).
|
||||
pub(super) fn with_pointer_ref<R>(&self, f: impl FnOnce(&P) -> R) -> R {
|
||||
// Safety:
|
||||
// - `self.raw.pointer_raw()` is originally returned from `P::into_ptr`
|
||||
// and as such is valid for `P::from_ptr`.
|
||||
// - This also allows us to not care whatever `f` panics or not.
|
||||
// - Even though we create a copy of the pointer, we store it inside
|
||||
// `ManuallyDrop` and only access it by-ref, so we don't double-drop.
|
||||
//
|
||||
// Semantically this is just `f(&self.pointer)` (where `self.pointer`
|
||||
// is non-packed original pointer).
|
||||
//
|
||||
// Note that even though `CopyTaggedPtr` is only really expected to
|
||||
// work with `P: Copy`, we have to assume `P: ?Copy`, because
|
||||
// `CopyTaggedPtr` is used in the `TaggedPtr`'s implementation.
|
||||
let ptr = unsafe { ManuallyDrop::new(P::from_ptr(self.pointer_raw())) };
|
||||
f(&ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Copy for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + Copy,
|
||||
T: Tag,
|
||||
{
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Clone for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + Copy,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Deref for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
type Target = P::Target;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// Safety:
|
||||
// `pointer_raw` returns the original pointer from `P::into_ptr` which,
|
||||
// by the `Pointer`'s contract, must be valid.
|
||||
unsafe { self.pointer_raw().as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> DerefMut for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + DerefMut,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// Safety:
|
||||
// `pointer_raw` returns the original pointer from `P::into_ptr` which,
|
||||
// by the `Pointer`'s contract, must be valid for writes if
|
||||
// `P: DerefMut`.
|
||||
unsafe { self.pointer_raw().as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> fmt::Debug for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + fmt::Debug,
|
||||
T: Tag + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.with_pointer_ref(|ptr| {
|
||||
f.debug_struct("CopyTaggedPtr").field("pointer", ptr).field("tag", &self.tag()).finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> PartialEq for CopyTaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.packed == other.packed
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Eq for CopyTaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
}
|
||||
|
||||
impl<P, T> Hash for CopyTaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.packed.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, HCX, const CP: bool> HashStable<HCX> for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + HashStable<HCX>,
|
||||
T: Tag + HashStable<HCX>,
|
||||
{
|
||||
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
|
||||
self.with_pointer_ref(|ptr| ptr.hash_stable(hcx, hasher));
|
||||
self.tag().hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// `CopyTaggedPtr<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
|
||||
// it's ok to implement `Sync` as long as `P: Sync, T: Sync`
|
||||
unsafe impl<P, T, const CP: bool> Sync for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Sync + Pointer,
|
||||
T: Sync + Tag,
|
||||
{
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// `CopyTaggedPtr<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
|
||||
// it's ok to implement `Send` as long as `P: Send, T: Send`
|
||||
unsafe impl<P, T, const CP: bool> Send for CopyTaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Send + Pointer,
|
||||
T: Send + Tag,
|
||||
{
|
||||
}
|
||||
|
||||
/// Test that `new` does not compile if there is not enough alignment for the
|
||||
/// tag in the pointer.
|
||||
///
|
||||
/// ```compile_fail,E0080
|
||||
/// use rustc_data_structures::tagged_ptr::{CopyTaggedPtr, Tag};
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// enum Tag2 { B00 = 0b00, B01 = 0b01, B10 = 0b10, B11 = 0b11 };
|
||||
///
|
||||
/// unsafe impl Tag for Tag2 {
|
||||
/// const BITS: u32 = 2;
|
||||
///
|
||||
/// fn into_usize(self) -> usize { todo!() }
|
||||
/// unsafe fn from_usize(tag: usize) -> Self { todo!() }
|
||||
/// }
|
||||
///
|
||||
/// let value = 12u16;
|
||||
/// let reference = &value;
|
||||
/// let tag = Tag2::B01;
|
||||
///
|
||||
/// let _ptr = CopyTaggedPtr::<_, _, true>::new(reference, tag);
|
||||
/// ```
|
||||
// For some reason miri does not get the compile error
|
||||
// probably it `check`s instead of `build`ing?
|
||||
#[cfg(not(miri))]
|
||||
const _: () = ();
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -1,50 +0,0 @@
|
|||
use std::ptr;
|
||||
|
||||
use crate::hashes::Hash128;
|
||||
use crate::stable_hasher::{HashStable, StableHasher};
|
||||
use crate::tagged_ptr::{CopyTaggedPtr, Pointer, Tag, Tag2};
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let value = 12u32;
|
||||
let reference = &value;
|
||||
let tag = Tag2::B01;
|
||||
|
||||
let ptr = tag_ptr(reference, tag);
|
||||
|
||||
assert_eq!(ptr.tag(), tag);
|
||||
assert_eq!(*ptr, 12);
|
||||
assert!(ptr::eq(ptr.pointer(), reference));
|
||||
|
||||
let copy = ptr;
|
||||
|
||||
let mut ptr = ptr;
|
||||
ptr.set_tag(Tag2::B00);
|
||||
assert_eq!(ptr.tag(), Tag2::B00);
|
||||
|
||||
assert_eq!(copy.tag(), tag);
|
||||
assert_eq!(*copy, 12);
|
||||
assert!(ptr::eq(copy.pointer(), reference));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stable_hash_hashes_as_tuple() {
|
||||
let hash_packed = {
|
||||
let mut hasher = StableHasher::new();
|
||||
tag_ptr(&12, Tag2::B11).hash_stable(&mut (), &mut hasher);
|
||||
hasher.finish::<Hash128>()
|
||||
};
|
||||
|
||||
let hash_tupled = {
|
||||
let mut hasher = StableHasher::new();
|
||||
(&12, Tag2::B11).hash_stable(&mut (), &mut hasher);
|
||||
hasher.finish::<Hash128>()
|
||||
};
|
||||
|
||||
assert_eq!(hash_packed, hash_tupled);
|
||||
}
|
||||
|
||||
/// Helper to create tagged pointers without specifying `COMPARE_PACKED` if it does not matter.
|
||||
fn tag_ptr<P: Pointer, T: Tag>(ptr: P, tag: T) -> CopyTaggedPtr<P, T, true> {
|
||||
CopyTaggedPtr::new(ptr, tag)
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::{CopyTaggedPtr, Pointer, Tag};
|
||||
use crate::stable_hasher::{HashStable, StableHasher};
|
||||
|
||||
/// A tagged pointer that supports pointers that implement [`Drop`].
|
||||
///
|
||||
/// This is essentially `{ pointer: P, tag: T }` packed in a single pointer.
|
||||
///
|
||||
/// You should use [`CopyTaggedPtr`] instead of the this type in all cases
|
||||
/// where `P` implements [`Copy`].
|
||||
///
|
||||
/// If `COMPARE_PACKED` is true, then the pointers will be compared and hashed without
|
||||
/// unpacking. Otherwise we don't implement [`PartialEq`], [`Eq`] and [`Hash`];
|
||||
/// if you want that, wrap the [`TaggedPtr`].
|
||||
pub struct TaggedPtr<P, T, const COMPARE_PACKED: bool>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
raw: CopyTaggedPtr<P, T, COMPARE_PACKED>,
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
/// Tags `pointer` with `tag`.
|
||||
#[inline]
|
||||
pub fn new(pointer: P, tag: T) -> Self {
|
||||
TaggedPtr { raw: CopyTaggedPtr::new(pointer, tag) }
|
||||
}
|
||||
|
||||
/// Retrieves the tag.
|
||||
#[inline]
|
||||
pub fn tag(&self) -> T {
|
||||
self.raw.tag()
|
||||
}
|
||||
|
||||
/// Sets the tag to a new value.
|
||||
#[inline]
|
||||
pub fn set_tag(&mut self, tag: T) {
|
||||
self.raw.set_tag(tag)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Clone for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + Clone,
|
||||
T: Tag,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
let ptr = self.raw.with_pointer_ref(P::clone);
|
||||
|
||||
Self::new(ptr, self.tag())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Deref for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
type Target = P::Target;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.raw.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> DerefMut for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + DerefMut,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.raw.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> Drop for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
// No need to drop the tag, as it's Copy
|
||||
unsafe {
|
||||
drop(P::from_ptr(self.raw.pointer_raw()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, const CP: bool> fmt::Debug for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + fmt::Debug,
|
||||
T: Tag + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.raw.with_pointer_ref(|ptr| {
|
||||
f.debug_struct("TaggedPtr").field("pointer", ptr).field("tag", &self.tag()).finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> PartialEq for TaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.raw.eq(&other.raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Eq for TaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
}
|
||||
|
||||
impl<P, T> Hash for TaggedPtr<P, T, true>
|
||||
where
|
||||
P: Pointer,
|
||||
T: Tag,
|
||||
{
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.raw.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T, HCX, const CP: bool> HashStable<HCX> for TaggedPtr<P, T, CP>
|
||||
where
|
||||
P: Pointer + HashStable<HCX>,
|
||||
T: Tag + HashStable<HCX>,
|
||||
{
|
||||
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
|
||||
self.raw.hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that `new` does not compile if there is not enough alignment for the
|
||||
/// tag in the pointer.
|
||||
///
|
||||
/// ```compile_fail,E0080
|
||||
/// use rustc_data_structures::tagged_ptr::{TaggedPtr, Tag};
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// enum Tag2 { B00 = 0b00, B01 = 0b01, B10 = 0b10, B11 = 0b11 };
|
||||
///
|
||||
/// unsafe impl Tag for Tag2 {
|
||||
/// const BITS: u32 = 2;
|
||||
///
|
||||
/// fn into_usize(self) -> usize { todo!() }
|
||||
/// unsafe fn from_usize(tag: usize) -> Self { todo!() }
|
||||
/// }
|
||||
///
|
||||
/// let value = 12u16;
|
||||
/// let reference = &value;
|
||||
/// let tag = Tag2::B01;
|
||||
///
|
||||
/// let _ptr = TaggedPtr::<_, _, true>::new(reference, tag);
|
||||
/// ```
|
||||
// For some reason miri does not get the compile error
|
||||
// probably it `check`s instead of `build`ing?
|
||||
#[cfg(not(miri))]
|
||||
const _: () = ();
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -1,72 +0,0 @@
|
|||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::tagged_ptr::{Pointer, Tag, Tag2, TaggedPtr};
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let value = 12u32;
|
||||
let reference = &value;
|
||||
let tag = Tag2::B01;
|
||||
|
||||
let ptr = tag_ptr(reference, tag);
|
||||
|
||||
assert_eq!(ptr.tag(), tag);
|
||||
assert_eq!(*ptr, 12);
|
||||
|
||||
let clone = ptr.clone();
|
||||
assert_eq!(clone.tag(), tag);
|
||||
assert_eq!(*clone, 12);
|
||||
|
||||
let mut ptr = ptr;
|
||||
ptr.set_tag(Tag2::B00);
|
||||
assert_eq!(ptr.tag(), Tag2::B00);
|
||||
|
||||
assert_eq!(clone.tag(), tag);
|
||||
assert_eq!(*clone, 12);
|
||||
assert!(ptr::eq(&*ptr, &*clone))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxed() {
|
||||
let value = 12u32;
|
||||
let boxed = Box::new(value);
|
||||
let tag = Tag2::B01;
|
||||
|
||||
let ptr = tag_ptr(boxed, tag);
|
||||
|
||||
assert_eq!(ptr.tag(), tag);
|
||||
assert_eq!(*ptr, 12);
|
||||
|
||||
let clone = ptr.clone();
|
||||
assert_eq!(clone.tag(), tag);
|
||||
assert_eq!(*clone, 12);
|
||||
|
||||
let mut ptr = ptr;
|
||||
ptr.set_tag(Tag2::B00);
|
||||
assert_eq!(ptr.tag(), Tag2::B00);
|
||||
|
||||
assert_eq!(clone.tag(), tag);
|
||||
assert_eq!(*clone, 12);
|
||||
assert!(!ptr::eq(&*ptr, &*clone))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arclones() {
|
||||
let value = 12u32;
|
||||
let arc = Arc::new(value);
|
||||
let tag = Tag2::B01;
|
||||
|
||||
let ptr = tag_ptr(arc, tag);
|
||||
|
||||
assert_eq!(ptr.tag(), tag);
|
||||
assert_eq!(*ptr, 12);
|
||||
|
||||
let clone = ptr.clone();
|
||||
assert!(ptr::eq(&*ptr, &*clone))
|
||||
}
|
||||
|
||||
/// Helper to create tagged pointers without specifying `COMPARE_PACKED` if it does not matter.
|
||||
fn tag_ptr<P: Pointer, T: Tag>(ptr: P, tag: T) -> TaggedPtr<P, T, true> {
|
||||
TaggedPtr::new(ptr, tag)
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
/// Implements [`Tag`] for a given type.
|
||||
///
|
||||
/// You can use `impl_tag` on structs and enums.
|
||||
/// You need to specify the type and all its possible values,
|
||||
/// which can only be paths with optional fields.
|
||||
///
|
||||
/// [`Tag`]: crate::tagged_ptr::Tag
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(macro_metavar_expr)]
|
||||
/// use rustc_data_structures::{impl_tag, tagged_ptr::Tag};
|
||||
///
|
||||
/// #[derive(Copy, Clone, PartialEq, Debug)]
|
||||
/// enum SomeTag {
|
||||
/// A,
|
||||
/// B,
|
||||
/// X { v: bool },
|
||||
/// Y(bool, bool),
|
||||
/// }
|
||||
///
|
||||
/// impl_tag! {
|
||||
/// // The type for which the `Tag` will be implemented
|
||||
/// impl Tag for SomeTag;
|
||||
/// // You need to specify all possible tag values:
|
||||
/// SomeTag::A, // 0
|
||||
/// SomeTag::B, // 1
|
||||
/// // For variants with fields, you need to specify the fields:
|
||||
/// SomeTag::X { v: true }, // 2
|
||||
/// SomeTag::X { v: false }, // 3
|
||||
/// // For tuple variants use named syntax:
|
||||
/// SomeTag::Y { 0: true, 1: true }, // 4
|
||||
/// SomeTag::Y { 0: false, 1: true }, // 5
|
||||
/// SomeTag::Y { 0: true, 1: false }, // 6
|
||||
/// SomeTag::Y { 0: false, 1: false }, // 7
|
||||
/// }
|
||||
///
|
||||
/// // Tag values are assigned in order:
|
||||
/// assert_eq!(SomeTag::A.into_usize(), 0);
|
||||
/// assert_eq!(SomeTag::X { v: false }.into_usize(), 3);
|
||||
/// assert_eq!(SomeTag::Y(false, true).into_usize(), 5);
|
||||
///
|
||||
/// assert_eq!(unsafe { SomeTag::from_usize(1) }, SomeTag::B);
|
||||
/// assert_eq!(unsafe { SomeTag::from_usize(2) }, SomeTag::X { v: true });
|
||||
/// assert_eq!(unsafe { SomeTag::from_usize(7) }, SomeTag::Y(false, false));
|
||||
/// ```
|
||||
///
|
||||
/// Structs are supported:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(macro_metavar_expr)]
|
||||
/// # use rustc_data_structures::impl_tag;
|
||||
/// #[derive(Copy, Clone)]
|
||||
/// struct Flags { a: bool, b: bool }
|
||||
///
|
||||
/// impl_tag! {
|
||||
/// impl Tag for Flags;
|
||||
/// Flags { a: true, b: true },
|
||||
/// Flags { a: false, b: true },
|
||||
/// Flags { a: true, b: false },
|
||||
/// Flags { a: false, b: false },
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Not specifying all values results in a compile error:
|
||||
///
|
||||
/// ```compile_fail,E0004
|
||||
/// #![feature(macro_metavar_expr)]
|
||||
/// # use rustc_data_structures::impl_tag;
|
||||
/// #[derive(Copy, Clone)]
|
||||
/// enum E {
|
||||
/// A,
|
||||
/// B,
|
||||
/// }
|
||||
///
|
||||
/// impl_tag! {
|
||||
/// impl Tag for E;
|
||||
/// E::A,
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! impl_tag {
|
||||
(
|
||||
impl Tag for $Self:ty;
|
||||
$(
|
||||
$($path:ident)::* $( { $( $fields:tt )* })?,
|
||||
)*
|
||||
) => {
|
||||
// Safety:
|
||||
// `bits_for_tags` is called on the same `${index()}`-es as
|
||||
// `into_usize` returns, thus `BITS` constant is correct.
|
||||
unsafe impl $crate::tagged_ptr::Tag for $Self {
|
||||
const BITS: u32 = $crate::tagged_ptr::bits_for_tags(&[
|
||||
$(
|
||||
${index()},
|
||||
$( ${ignore($path)} )*
|
||||
)*
|
||||
]);
|
||||
|
||||
#[inline]
|
||||
fn into_usize(self) -> usize {
|
||||
// This forbids use of repeating patterns (`Enum::V`&`Enum::V`, etc)
|
||||
// (or at least it should, see <https://github.com/rust-lang/rust/issues/110613>)
|
||||
#[forbid(unreachable_patterns)]
|
||||
match self {
|
||||
// `match` is doing heavy lifting here, by requiring exhaustiveness
|
||||
$(
|
||||
$($path)::* $( { $( $fields )* } )? => ${index()},
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_usize(tag: usize) -> Self {
|
||||
match tag {
|
||||
$(
|
||||
${index()} => $($path)::* $( { $( $fields )* } )?,
|
||||
)*
|
||||
|
||||
// Safety:
|
||||
// `into_usize` only returns `${index()}` of the same
|
||||
// repetition as we are filtering above, thus if this is
|
||||
// reached, the safety contract of this function was
|
||||
// already breached.
|
||||
_ => unsafe {
|
||||
debug_assert!(
|
||||
false,
|
||||
"invalid tag: {tag}\
|
||||
(this is a bug in the caller of `from_usize`)"
|
||||
);
|
||||
std::hint::unreachable_unchecked()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -1,34 +0,0 @@
|
|||
#[test]
|
||||
fn bits_constant() {
|
||||
use crate::tagged_ptr::Tag;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Unit;
|
||||
impl_tag! { impl Tag for Unit; Unit, }
|
||||
assert_eq!(Unit::BITS, 0);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Enum3 {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
impl_tag! { impl Tag for Enum3; Enum3::A, Enum3::B, Enum3::C, }
|
||||
assert_eq!(Enum3::BITS, 2);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Eight(bool, bool, bool);
|
||||
impl_tag! {
|
||||
impl Tag for Eight;
|
||||
Eight { 0: true, 1: true, 2: true },
|
||||
Eight { 0: true, 1: true, 2: false },
|
||||
Eight { 0: true, 1: false, 2: true },
|
||||
Eight { 0: true, 1: false, 2: false },
|
||||
Eight { 0: false, 1: true, 2: true },
|
||||
Eight { 0: false, 1: true, 2: false },
|
||||
Eight { 0: false, 1: false, 2: true },
|
||||
Eight { 0: false, 1: false, 2: false },
|
||||
}
|
||||
|
||||
assert_eq!(Eight::BITS, 3);
|
||||
}
|
105
compiler/rustc_data_structures/src/tagged_ptr/tests.rs
Normal file
105
compiler/rustc_data_structures/src/tagged_ptr/tests.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use std::ptr;
|
||||
|
||||
use super::*;
|
||||
use crate::hashes::Hash128;
|
||||
use crate::stable_hasher::{HashStable, StableHasher};
|
||||
|
||||
/// A tag type used in [`TaggedRef`] tests.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Tag2 {
|
||||
B00 = 0b00,
|
||||
B01 = 0b01,
|
||||
B10 = 0b10,
|
||||
B11 = 0b11,
|
||||
}
|
||||
|
||||
unsafe impl Tag for Tag2 {
|
||||
const BITS: u32 = 2;
|
||||
|
||||
fn into_usize(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
|
||||
unsafe fn from_usize(tag: usize) -> Self {
|
||||
match tag {
|
||||
0b00 => Tag2::B00,
|
||||
0b01 => Tag2::B01,
|
||||
0b10 => Tag2::B10,
|
||||
0b11 => Tag2::B11,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<HCX> crate::stable_hasher::HashStable<HCX> for Tag2 {
|
||||
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut crate::stable_hasher::StableHasher) {
|
||||
(*self as u8).hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let value = 12u32;
|
||||
let reference = &value;
|
||||
let tag = Tag2::B01;
|
||||
|
||||
let ptr = TaggedRef::new(reference, tag);
|
||||
|
||||
assert_eq!(ptr.tag(), tag);
|
||||
assert_eq!(*ptr, 12);
|
||||
assert!(ptr::eq(ptr.pointer(), reference));
|
||||
|
||||
let copy = ptr;
|
||||
|
||||
let mut ptr = ptr;
|
||||
ptr.set_tag(Tag2::B00);
|
||||
assert_eq!(ptr.tag(), Tag2::B00);
|
||||
|
||||
assert_eq!(copy.tag(), tag);
|
||||
assert_eq!(*copy, 12);
|
||||
assert!(ptr::eq(copy.pointer(), reference));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stable_hash_hashes_as_tuple() {
|
||||
let hash_packed = {
|
||||
let mut hasher = StableHasher::new();
|
||||
TaggedRef::new(&12, Tag2::B11).hash_stable(&mut (), &mut hasher);
|
||||
hasher.finish::<Hash128>()
|
||||
};
|
||||
|
||||
let hash_tupled = {
|
||||
let mut hasher = StableHasher::new();
|
||||
(&12, Tag2::B11).hash_stable(&mut (), &mut hasher);
|
||||
hasher.finish::<Hash128>()
|
||||
};
|
||||
|
||||
assert_eq!(hash_packed, hash_tupled);
|
||||
}
|
||||
|
||||
/// Test that `new` does not compile if there is not enough alignment for the
|
||||
/// tag in the pointer.
|
||||
///
|
||||
/// ```compile_fail,E0080
|
||||
/// use rustc_data_structures::tagged_ptr::{TaggedRef, Tag};
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// enum Tag2 { B00 = 0b00, B01 = 0b01, B10 = 0b10, B11 = 0b11 };
|
||||
///
|
||||
/// unsafe impl Tag for Tag2 {
|
||||
/// const BITS: u32 = 2;
|
||||
///
|
||||
/// fn into_usize(self) -> usize { todo!() }
|
||||
/// unsafe fn from_usize(tag: usize) -> Self { todo!() }
|
||||
/// }
|
||||
///
|
||||
/// let value = 12u16;
|
||||
/// let reference = &value;
|
||||
/// let tag = Tag2::B01;
|
||||
///
|
||||
/// let _ptr = TaggedRef::<_, _, true>::new(reference, tag);
|
||||
/// ```
|
||||
// For some reason miri does not get the compile error
|
||||
// probably it `check`s instead of `build`ing?
|
||||
#[cfg(not(miri))]
|
||||
const _: () = ();
|
|
@ -2460,16 +2460,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
spans.push_span_label(
|
||||
param.span,
|
||||
format!(
|
||||
"{} {} to match the {} type of this parameter",
|
||||
"{} need{} to match the {} type of this parameter",
|
||||
display_list_with_comma_and(&other_param_matched_names),
|
||||
format!(
|
||||
"need{}",
|
||||
pluralize!(if other_param_matched_names.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
})
|
||||
),
|
||||
pluralize!(if other_param_matched_names.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}),
|
||||
matched_ty,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ declare_lint! {
|
|||
/// ### Example
|
||||
///
|
||||
/// ```rust,edition2018
|
||||
/// #![feature(unqualified_local_imports)]
|
||||
/// #![warn(unqualified_local_imports)]
|
||||
///
|
||||
/// mod localmod {
|
||||
|
|
|
@ -2671,6 +2671,7 @@ declare_lint! {
|
|||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(strict_provenance_lints)]
|
||||
/// #![warn(fuzzy_provenance_casts)]
|
||||
///
|
||||
/// fn main() {
|
||||
|
@ -2714,6 +2715,7 @@ declare_lint! {
|
|||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(strict_provenance_lints)]
|
||||
/// #![warn(lossy_provenance_casts)]
|
||||
///
|
||||
/// fn main() {
|
||||
|
@ -4033,6 +4035,8 @@ declare_lint! {
|
|||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// // This lint is intentionally used to test the compiler's behavior
|
||||
/// // when an unstable lint is enabled without the corresponding feature gate.
|
||||
/// #![allow(test_unstable_lint)]
|
||||
/// ```
|
||||
///
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::arena::Arena;
|
|||
/// pointer.
|
||||
/// - Because of this, you cannot get a `List<T>` that is a sub-list of another
|
||||
/// `List<T>`. You can get a sub-slice `&[T]`, however.
|
||||
/// - `List<T>` can be used with `CopyTaggedPtr`, which is useful within
|
||||
/// - `List<T>` can be used with `TaggedRef`, which is useful within
|
||||
/// structs whose size must be minimized.
|
||||
/// - Because of the uniqueness assumption, we can use the address of a
|
||||
/// `List<T>` for faster equality comparisons and hashing.
|
||||
|
|
|
@ -347,7 +347,7 @@ pub fn strip_generics_from_path(path_str: &str) -> Result<Box<str>, MalformedGen
|
|||
|
||||
/// Returns whether the first doc-comment is an inner attribute.
|
||||
///
|
||||
//// If there are no doc-comments, return true.
|
||||
/// If there are no doc-comments, return true.
|
||||
/// FIXME(#78591): Support both inner and outer attributes on the same item.
|
||||
pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
|
||||
attrs
|
||||
|
|
|
@ -48,8 +48,8 @@ macro_rules! vec {
|
|||
);
|
||||
($($x:expr),+ $(,)?) => (
|
||||
<[_]>::into_vec(
|
||||
// Using the intrinsic produces a dramatic improvement in compile
|
||||
// time when constructing arrays with many elements.
|
||||
// Using the intrinsic produces a dramatic improvement in stack usage for
|
||||
// unoptimized programs using this code path to construct large Vecs.
|
||||
$crate::boxed::box_new([$($x),+])
|
||||
)
|
||||
);
|
||||
|
|
|
@ -386,13 +386,37 @@ impl Step for CI {
|
|||
let library_clippy_cfg = LintConfig {
|
||||
allow: vec!["clippy::all".into()],
|
||||
warn: vec![],
|
||||
deny: vec!["clippy::correctness".into()],
|
||||
deny: vec![
|
||||
"clippy::correctness".into(),
|
||||
"clippy::char_lit_as_u8".into(),
|
||||
"clippy::four_forward_slashes".into(),
|
||||
"clippy::needless_bool".into(),
|
||||
"clippy::needless_bool_assign".into(),
|
||||
"clippy::non_minimal_cfg".into(),
|
||||
"clippy::print_literal".into(),
|
||||
"clippy::same_item_push".into(),
|
||||
"clippy::single_char_add_str".into(),
|
||||
"clippy::to_string_in_format_args".into(),
|
||||
],
|
||||
forbid: vec![],
|
||||
};
|
||||
let compiler_clippy_cfg = LintConfig {
|
||||
allow: vec!["clippy::all".into()],
|
||||
warn: vec![],
|
||||
deny: vec!["clippy::correctness".into(), "clippy::clone_on_ref_ptr".into()],
|
||||
deny: vec![
|
||||
"clippy::correctness".into(),
|
||||
"clippy::char_lit_as_u8".into(),
|
||||
"clippy::clone_on_ref_ptr".into(),
|
||||
"clippy::format_in_format_args".into(),
|
||||
"clippy::four_forward_slashes".into(),
|
||||
"clippy::needless_bool".into(),
|
||||
"clippy::needless_bool_assign".into(),
|
||||
"clippy::non_minimal_cfg".into(),
|
||||
"clippy::print_literal".into(),
|
||||
"clippy::same_item_push".into(),
|
||||
"clippy::single_char_add_str".into(),
|
||||
"clippy::to_string_in_format_args".into(),
|
||||
],
|
||||
forbid: vec![],
|
||||
};
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ struct CacheBuilder<'a, 'tcx> {
|
|||
/// This field is used to prevent duplicated impl blocks.
|
||||
impl_ids: DefIdMap<DefIdSet>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
is_json_output: bool,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
|
@ -184,8 +185,13 @@ impl Cache {
|
|||
}
|
||||
|
||||
let (krate, mut impl_ids) = {
|
||||
let mut cache_builder =
|
||||
CacheBuilder { tcx, cache: &mut cx.cache, impl_ids: Default::default() };
|
||||
let is_json_output = cx.is_json_output();
|
||||
let mut cache_builder = CacheBuilder {
|
||||
tcx,
|
||||
cache: &mut cx.cache,
|
||||
impl_ids: Default::default(),
|
||||
is_json_output,
|
||||
};
|
||||
krate = cache_builder.fold_crate(krate);
|
||||
(krate, cache_builder.impl_ids)
|
||||
};
|
||||
|
@ -307,12 +313,13 @@ impl DocFolder for CacheBuilder<'_, '_> {
|
|||
| clean::ProcMacroItem(..)
|
||||
| clean::VariantItem(..) => {
|
||||
use rustc_data_structures::fx::IndexEntry as Entry;
|
||||
if !self.cache.stripped_mod
|
||||
&& !matches!(
|
||||
item.stability.map(|stab| stab.level),
|
||||
Some(StabilityLevel::Stable { allowed_through_unstable_modules: true, .. })
|
||||
)
|
||||
{
|
||||
|
||||
let skip_because_unstable = matches!(
|
||||
item.stability.map(|stab| stab.level),
|
||||
Some(StabilityLevel::Stable { allowed_through_unstable_modules: true, .. })
|
||||
);
|
||||
|
||||
if (!self.cache.stripped_mod && !skip_because_unstable) || self.is_json_output {
|
||||
// Re-exported items mean that the same id can show up twice
|
||||
// in the rustdoc ast that we're looking at. We know,
|
||||
// however, that a re-exported item doesn't show up in the
|
||||
|
|
|
@ -65,6 +65,11 @@ enum CommandKind {
|
|||
/// Checks the path doesn't exist.
|
||||
HasNotPath,
|
||||
|
||||
/// `//@ !has <path> <value>`
|
||||
///
|
||||
/// Checks the path exists, but doesn't have the given value.
|
||||
HasNotValue { value: String },
|
||||
|
||||
/// `//@ is <path> <value>`
|
||||
///
|
||||
/// Check the path is the given value.
|
||||
|
@ -128,10 +133,11 @@ impl CommandKind {
|
|||
[_path, value] => Self::HasValue { value: value.clone() },
|
||||
_ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"),
|
||||
},
|
||||
("has", true) => {
|
||||
assert_eq!(args.len(), 1, "args={args:?}");
|
||||
Self::HasNotPath
|
||||
}
|
||||
("has", true) => match args {
|
||||
[_path] => Self::HasNotPath,
|
||||
[_path, value] => Self::HasNotValue { value: value.clone() },
|
||||
_ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"),
|
||||
},
|
||||
|
||||
(_, false) if KNOWN_DIRECTIVE_NAMES.contains(&command_name) => {
|
||||
return None;
|
||||
|
@ -223,6 +229,19 @@ fn check_command(command: &Command, cache: &mut Cache) -> Result<(), String> {
|
|||
return Err(format!("matched to {matches:?}, which didn't contain {want_value:?}"));
|
||||
}
|
||||
}
|
||||
CommandKind::HasNotValue { value } => {
|
||||
let wantnt_value = string_to_value(value, cache);
|
||||
if matches.contains(&wantnt_value.as_ref()) {
|
||||
return Err(format!(
|
||||
"matched to {matches:?}, which contains unwanted {wantnt_value:?}"
|
||||
));
|
||||
} else if matches.is_empty() {
|
||||
return Err(format!(
|
||||
"got no matches, but expected some matched (not containing {wantnt_value:?}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
CommandKind::Is { value } => {
|
||||
let want_value = string_to_value(value, cache);
|
||||
let matched = get_one(&matches)?;
|
||||
|
|
|
@ -303,6 +303,12 @@ impl<'a> Validator<'a> {
|
|||
PathKind::Trait => self.add_trait_or_alias_id(&x.id),
|
||||
PathKind::Type => self.add_type_id(&x.id),
|
||||
}
|
||||
|
||||
// FIXME: More robust support for checking things in $.index also exist in $.paths
|
||||
if !self.krate.paths.contains_key(&x.id) {
|
||||
self.fail(&x.id, ErrorKind::Custom(format!("No entry in '$.paths' for {x:?}")));
|
||||
}
|
||||
|
||||
if let Some(args) = &x.args {
|
||||
self.check_generic_args(&**args);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use rustc_hash::FxHashMap;
|
||||
use rustdoc_json_types::{FORMAT_VERSION, Item, ItemKind, Visibility};
|
||||
use rustdoc_json_types::{Abi, FORMAT_VERSION, FunctionHeader, Item, ItemKind, Visibility};
|
||||
|
||||
use super::*;
|
||||
use crate::json_find::SelectorPart;
|
||||
|
@ -102,6 +102,101 @@ fn errors_on_local_in_paths_and_not_index() {
|
|||
}]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_missing_path() {
|
||||
// crate-name=foo
|
||||
// ```
|
||||
// pub struct Bar;
|
||||
// pub fn mk_bar() -> Bar { ... }
|
||||
// ```
|
||||
|
||||
let generics = Generics { params: vec![], where_predicates: vec![] };
|
||||
|
||||
let krate = Crate {
|
||||
root: Id(0),
|
||||
crate_version: None,
|
||||
includes_private: false,
|
||||
index: FxHashMap::from_iter([
|
||||
(Id(0), Item {
|
||||
id: Id(0),
|
||||
crate_id: 0,
|
||||
name: Some("foo".to_owned()),
|
||||
span: None,
|
||||
visibility: Visibility::Public,
|
||||
docs: None,
|
||||
links: FxHashMap::default(),
|
||||
attrs: Vec::new(),
|
||||
deprecation: None,
|
||||
inner: ItemEnum::Module(Module {
|
||||
is_crate: true,
|
||||
items: vec![Id(1), Id(2)],
|
||||
is_stripped: false,
|
||||
}),
|
||||
}),
|
||||
(Id(1), Item {
|
||||
id: Id(0),
|
||||
crate_id: 0,
|
||||
name: Some("Bar".to_owned()),
|
||||
span: None,
|
||||
visibility: Visibility::Public,
|
||||
docs: None,
|
||||
links: FxHashMap::default(),
|
||||
attrs: Vec::new(),
|
||||
deprecation: None,
|
||||
inner: ItemEnum::Struct(Struct {
|
||||
kind: StructKind::Unit,
|
||||
generics: generics.clone(),
|
||||
impls: vec![],
|
||||
}),
|
||||
}),
|
||||
(Id(2), Item {
|
||||
id: Id(0),
|
||||
crate_id: 0,
|
||||
name: Some("mk_bar".to_owned()),
|
||||
span: None,
|
||||
visibility: Visibility::Public,
|
||||
docs: None,
|
||||
links: FxHashMap::default(),
|
||||
attrs: Vec::new(),
|
||||
deprecation: None,
|
||||
inner: ItemEnum::Function(Function {
|
||||
sig: FunctionSignature {
|
||||
inputs: vec![],
|
||||
output: Some(Type::ResolvedPath(Path {
|
||||
name: "Bar".to_owned(),
|
||||
id: Id(1),
|
||||
args: None,
|
||||
})),
|
||||
is_c_variadic: false,
|
||||
},
|
||||
generics,
|
||||
header: FunctionHeader {
|
||||
is_const: false,
|
||||
is_unsafe: false,
|
||||
is_async: false,
|
||||
abi: Abi::Rust,
|
||||
},
|
||||
has_body: true,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
paths: FxHashMap::from_iter([(Id(0), ItemSummary {
|
||||
crate_id: 0,
|
||||
path: vec!["foo".to_owned()],
|
||||
kind: ItemKind::Module,
|
||||
})]),
|
||||
external_crates: FxHashMap::default(),
|
||||
format_version: rustdoc_json_types::FORMAT_VERSION,
|
||||
};
|
||||
|
||||
check(&krate, &[Error {
|
||||
kind: ErrorKind::Custom(
|
||||
r#"No entry in '$.paths' for Path { name: "Bar", id: Id(1), args: None }"#.to_owned(),
|
||||
),
|
||||
id: Id(1),
|
||||
}]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "LOCAL_CRATE_ID is wrong"]
|
||||
fn checks_local_crate_id_is_correct() {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// We want to control use declaration ordering and spacing (and preserve use group comments), so
|
||||
// skip rustfmt on this file.
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
#![warn(unreachable_pub)]
|
||||
|
||||
mod command;
|
||||
mod macros;
|
||||
|
|
|
@ -12,3 +12,9 @@ mod inner {
|
|||
pub use inner::Public;
|
||||
|
||||
//@ ismany "$.index[*][?(@.name=='simple_private')].inner.module.items[*]" $use_id
|
||||
|
||||
// Test for https://github.com/rust-lang/rust/issues/135309
|
||||
//@ has "$.paths[*][?(@.kind=='module')].path" '["simple_private"]'
|
||||
//@ !has "$.paths[*].path" '["simple_private", "inner"]'
|
||||
//@ has "$.paths[*][?(@.kind=='struct')].path" '["simple_private", "inner", "Public"]'
|
||||
//@ !has "$.paths[*].path" '["simple_private", "Public"]'
|
||||
|
|
|
@ -14,3 +14,8 @@ pub mod inner {
|
|||
pub use inner::Public;
|
||||
|
||||
//@ ismany "$.index[*][?(@.name=='simple_public')].inner.module.items[*]" $import_id $inner_id
|
||||
|
||||
//@ has "$.paths[*][?(@.kind=='module')].path" '["simple_public"]'
|
||||
//@ has "$.paths[*][?(@.kind=='module')].path" '["simple_public", "inner"]'
|
||||
//@ has "$.paths[*][?(@.kind=='struct')].path" '["simple_public", "inner", "Public"]'
|
||||
//@ !has "$.paths[*].path" '["simple_public", "Public"]'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue