diff --git a/.mergify.yml b/.mergify.yml index b88ad04..4cc93b9 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -3,11 +3,10 @@ pull_request_rules: conditions: - base=master - status-success=tests - - label!=work-in-progress + - "label!=work in progress" - "#approved-reviews-by>=1" - "#review-requested=0" - "#changes-requested-reviews-by=0" - - "#commented-reviews-by=0" actions: merge: method: merge @@ -17,11 +16,10 @@ pull_request_rules: conditions: - base=master - status-success=tests - - label!=work-in-progress + - "label!=work in progress" - author=alecmocatta # https://github.com/Mergifyio/mergify-engine/issues/451 - "#review-requested=0" - "#changes-requested-reviews-by=0" - - "#commented-reviews-by=0" actions: merge: method: merge @@ -36,10 +34,10 @@ pull_request_rules: - "title~=^WIP: .*" actions: label: - add: ["work-in-progress"] + add: ["work in progress"] - name: auto remove wip label conditions: - "-title~=^WIP: .*" actions: label: - remove: ["work-in-progress"] + remove: ["work in progress"] diff --git a/Cargo.toml b/Cargo.toml index c51b807..5f1e7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "metatype" -version = "0.1.2" +version = "0.2.0" license = "MIT OR Apache-2.0" authors = ["Alec Mocatta "] categories = ["development-tools","rust-patterns"] @@ -10,7 +10,7 @@ Helper methods to determine whether a type is `TraitObject`, `Slice` or `Concret """ repository = "https://github.com/alecmocatta/metatype" homepage = "https://github.com/alecmocatta/metatype" -documentation = "https://docs.rs/metatype/0.1.2" +documentation = "https://docs.rs/metatype/0.2.0" readme = "README.md" edition = "2018" diff --git a/README.md b/README.md index e5a72c5..64bde3c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![MIT / Apache 2.0 licensed](https://img.shields.io/crates/l/metatype.svg?maxAge=2592000)](#License) [![Build Status](https://dev.azure.com/alecmocatta/metatype/_apis/build/status/tests?branchName=master)](https://dev.azure.com/alecmocatta/metatype/_build/latest?branchName=master) -[Docs](https://docs.rs/metatype/0.1.2) +[Docs](https://docs.rs/metatype/0.2.0) Helper methods to determine whether a type is `TraitObject`, `Slice` or `Concrete`, and work with them respectively. @@ -16,24 +16,23 @@ assert_eq!(any::Any::METATYPE, MetaType::TraitObject); assert_eq!(<[u8]>::METATYPE, MetaType::Slice); let a: Box = Box::new(123); -assert_eq!((&*a).meta_type(), MetaType::Concrete); -let a: Box = a; -assert_eq!((&*a).meta_type(), MetaType::TraitObject); +assert_eq!(Type::meta_type(&*a), MetaType::Concrete); +let a: Box = a; +assert_eq!(Type::meta_type(&*a), MetaType::TraitObject); let a = [123,456]; -assert_eq!(a.meta_type(), MetaType::Concrete); +assert_eq!(Type::meta_type(&a), MetaType::Concrete); let a: &[i32] = &a; -assert_eq!(a.meta_type(), MetaType::Slice); +assert_eq!(Type::meta_type(a), MetaType::Slice); -let a: Box = Box::new(123); -// https://github.com/rust-lang/rust/issues/50318 -// let meta: TraitObject = (&*a).meta(); -// println!("vtable: {:?}", meta.vtable); +let a: Box = Box::new(123); +let meta: TraitObject = type_coerce(Type::meta(&*a)); +println!("vtable: {:?}", meta.vtable); ``` ## Note -This currently requires Rust nightly for the `raw` and `specialization` features. +This currently requires Rust nightly for the `raw`, `specialization` and `arbitrary_self_types` features. ## License Licensed under either of diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d7312da..c2fbdfe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,7 @@ jobs: endpoint: alecmocatta default: rust_toolchain: nightly - rust_lint_toolchain: nightly-2019-07-19 + rust_lint_toolchain: nightly-2019-10-17 rust_flags: '' rust_features: '' rust_target_check: '' diff --git a/src/lib.rs b/src/lib.rs index f9d2ef3..1310277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -//! Helper methods to determine whether a type is `TraitObject`, `Slice` or `Concrete`, and work with them respectively. +//! Helper methods to determine whether a type is `TraitObject`, `Slice` or +//! `Concrete`, and work with them respectively. //! //! # Examples //! @@ -10,41 +11,49 @@ //! assert_eq!(<[u8]>::METATYPE, MetaType::Slice); //! //! let a: Box = Box::new(123); -//! assert_eq!((&*a).meta_type(), MetaType::Concrete); +//! assert_eq!(Type::meta_type(&*a), MetaType::Concrete); //! let a: Box = a; -//! assert_eq!((&*a).meta_type(), MetaType::TraitObject); +//! assert_eq!(Type::meta_type(&*a), MetaType::TraitObject); //! //! let a = [123,456]; -//! assert_eq!(a.meta_type(), MetaType::Concrete); +//! assert_eq!(Type::meta_type(&a), MetaType::Concrete); //! let a: &[i32] = &a; -//! assert_eq!(a.meta_type(), MetaType::Slice); +//! assert_eq!(Type::meta_type(a), MetaType::Slice); //! //! let a: Box = Box::new(123); -//! // https://github.com/rust-lang/rust/issues/50318 -//! // let meta: TraitObject = (&*a).meta(); -//! // println!("vtable: {:?}", meta.vtable); +//! let meta: TraitObject = type_coerce(Type::meta(&*a)); +//! println!("vtable: {:?}", meta.vtable); //! ``` //! //! # Note //! -//! This currently requires Rust nightly for the `raw` and `specialization` features. +//! This currently requires Rust nightly for the `raw`, `specialization` and +//! `arbitrary_self_types` features. -#![doc(html_root_url = "https://docs.rs/metatype/0.1.2")] -#![feature(raw, box_syntax, specialization)] +#![doc(html_root_url = "https://docs.rs/metatype/0.2.0")] +#![feature(arbitrary_self_types)] +#![feature(raw)] +#![feature(slice_from_raw_parts)] +#![feature(specialization)] #![warn( missing_copy_implementations, missing_debug_implementations, missing_docs, - // trivial_casts, + trivial_casts, trivial_numeric_casts, unused_import_braces, unused_qualifications, unused_results, clippy::pedantic )] // from https://github.com/rust-unofficial/patterns/blob/master/anti_patterns/deny-warnings.md +#![allow( + clippy::must_use_candidate, + clippy::not_unsafe_ptr_arg_deref, + clippy::use_self +)] use std::{ - any, mem::{self, MaybeUninit}, raw + any::{type_name, TypeId}, hash::{Hash, Hasher}, marker::PhantomData, mem::{align_of, align_of_val, forget, size_of, size_of_val, transmute_copy}, ptr::{slice_from_raw_parts_mut, NonNull}, raw }; /// Implemented on all types, it provides helper methods to determine whether a type is `TraitObject`, `Slice` or `Concrete`, and work with them respectively. @@ -54,19 +63,19 @@ pub trait Type { /// Type of metadata for type. type Meta: 'static; /// Helper method describing whether a type is `TraitObject`, `Slice` or `Concrete`. - fn meta_type(&self) -> MetaType { + fn meta_type(self: *const Self) -> MetaType { Self::METATYPE } - /// Retrieve [TraitObject], [Slice] or [Concrete] meta data respectively for a type - fn meta(&self) -> Self::Meta; + /// Retrieve [`TraitObject`], [`Slice`] or [`Concrete`] meta data respectively for a type + fn meta(self: *const Self) -> Self::Meta; /// Retrieve pointer to the data - fn data(&self) -> *const (); + fn data(self: *const Self) -> *const (); /// Retrieve mut pointer to the data - fn data_mut(&mut self) -> *mut (); - /// Create a `Box` with the provided `Self::Meta` but with the allocated data uninitialized. - /// - /// See the ongoing discussion [Validity of Box\](https://github.com/rust-lang/unsafe-code-guidelines/issues/145) for validity. - unsafe fn uninitialized_box(t: Self::Meta) -> Box; + fn data_mut(self: *mut Self) -> *mut (); + /// Create a dangling non-null `*const Self` with the provided `Self::Meta`. + fn dangling(t: Self::Meta) -> NonNull; + /// Create a `*mut Self` with the provided `Self::Meta`. + fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self; } /// Meta type of a type #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -101,81 +110,51 @@ impl Type for T { #[doc(hidden)] default type Meta = TraitObject; #[inline] - default fn meta(&self) -> Self::Meta { - assert_eq!( - (mem::size_of::<&Self>(), mem::align_of::<&Self>()), - ( - mem::size_of::(), - mem::align_of::() - ) - ); - let trait_object: raw::TraitObject = unsafe { mem::transmute_copy(&self) }; - assert_eq!( - trait_object.data as *const (), - self as *const Self as *const () - ); + default fn meta(self: *const Self) -> Self::Meta { + let trait_object = unsafe { transmute_coerce::<*const Self, raw::TraitObject>(self) }; + assert_eq!(self as *const (), trait_object.data); let ret = TraitObject { vtable: unsafe { &*trait_object.vtable }, }; - assert_eq!( - any::TypeId::of::(), - any::TypeId::of::() - ); - unsafe { mem::transmute_copy(&ret) } + type_coerce(ret) } #[inline] - default fn data(&self) -> *const () { - assert_eq!( - (mem::size_of::<&Self>(), mem::align_of::<&Self>()), - ( - mem::size_of::(), - mem::align_of::() - ) - ); - let trait_object: raw::TraitObject = unsafe { mem::transmute_copy(&self) }; - assert_eq!( - trait_object.data as *const (), - self as *const Self as *const () - ); - self as *const Self as *const () + default fn data(self: *const Self) -> *const () { + let trait_object = unsafe { transmute_coerce::<*const Self, raw::TraitObject>(self) }; + assert_eq!(self as *const (), trait_object.data); + self as *const () } #[inline] - default fn data_mut(&mut self) -> *mut () { - assert_eq!( - (mem::size_of::<&Self>(), mem::align_of::<&Self>()), - ( - mem::size_of::(), - mem::align_of::() - ) - ); - let trait_object: raw::TraitObject = unsafe { mem::transmute_copy(&self) }; - assert_eq!(trait_object.data, self as *mut Self as *mut ()); - self as *mut Self as *mut () + default fn data_mut(self: *mut Self) -> *mut () { + let trait_object = unsafe { transmute_coerce::<*const Self, raw::TraitObject>(self) }; + assert_eq!(self as *mut (), trait_object.data); + self as *mut () } - default unsafe fn uninitialized_box(t: Self::Meta) -> Box { - assert_eq!( - any::TypeId::of::(), - any::TypeId::of::() - ); - assert_eq!( - (mem::size_of::<&Self>(), mem::align_of::<&Self>()), - ( - mem::size_of::(), - mem::align_of::() - ) - ); - - let t: TraitObject = mem::transmute_copy(&t); - let vtable = t.vtable as *const () as *mut (); - let mut data = { + #[inline] + default fn dangling(t: Self::Meta) -> NonNull { + let t: TraitObject = type_coerce(t); + // align_of_val requires a reference: https://github.com/rust-lang/rfcs/issues/2017 + // so to placate miri let's create one that's plausibly valid + let fake_thin = { #[repr(align(64))] struct Backing(u8); static BACKING: Backing = Backing(0); - &BACKING as *const _ as *mut () + let backing: *const _ = &BACKING; + backing as *mut () }; - let object: &Self = mem::transmute_copy(&raw::TraitObject { data, vtable }); - data = std::alloc::alloc(std::alloc::Layout::for_value(object)) as *mut (); - mem::transmute_copy(&raw::TraitObject { data, vtable }) + let dangling_unaligned: NonNull = + NonNull::new(Self::fatten(fake_thin, type_coerce(t))).unwrap(); + let dangling_unaligned: &Self = unsafe { dangling_unaligned.as_ref() }; + let align = align_of_val(dangling_unaligned); + NonNull::new(Self::fatten(align as _, type_coerce(t))).unwrap() + } + #[inline] + default fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self { + let t: TraitObject = type_coerce(t); + let vtable: *const () = t.vtable; + let vtable = vtable as *mut (); + let ret = raw::TraitObject { data: thin, vtable }; + unsafe { transmute_coerce::(ret) } } } #[doc(hidden)] @@ -183,19 +162,22 @@ impl Type for T { const METATYPE: MetaType = MetaType::Concrete; type Meta = Concrete; #[inline] - fn meta(&self) -> Self::Meta { + fn meta(self: *const Self) -> Self::Meta { Concrete } #[inline] - fn data(&self) -> *const () { - self as *const Self as *const () + fn data(self: *const Self) -> *const () { + self as *const () } #[inline] - fn data_mut(&mut self) -> *mut () { - self as *mut Self as *mut () + fn data_mut(self: *mut Self) -> *mut () { + self as *mut () + } + fn dangling(_t: Self::Meta) -> NonNull { + NonNull::dangling() } - unsafe fn uninitialized_box(_: Self::Meta) -> Box { - mem::transmute(box MaybeUninit::::uninit()) + fn fatten(thin: *mut (), _t: Self::Meta) -> *mut Self { + thin.cast() } } #[doc(hidden)] @@ -203,25 +185,28 @@ impl Type for [T] { const METATYPE: MetaType = MetaType::Slice; type Meta = Slice; #[inline] - fn meta(&self) -> Self::Meta { + fn meta(self: *const Self) -> Self::Meta { + let self_ = unsafe { &*self }; // https://github.com/rust-lang/rfcs/issues/2017 assert_eq!( - (mem::size_of_val(self), mem::align_of_val(self)), - (mem::size_of::() * self.len(), mem::align_of::()) + (size_of_val(self_), align_of_val(self_)), + (size_of::() * self_.len(), align_of::()) ); - Slice { len: self.len() } + Slice { len: self_.len() } } #[inline] - fn data(&self) -> *const () { - self.as_ptr() as *const () + fn data(self: *const Self) -> *const () { + self as *const () } #[inline] - fn data_mut(&mut self) -> *mut () { - self.as_mut_ptr() as *mut () + fn data_mut(self: *mut Self) -> *mut () { + self as *mut () } - unsafe fn uninitialized_box(t: Self::Meta) -> Box { - let mut backing = Vec::::with_capacity(t.len); - backing.set_len(t.len); - backing.into_boxed_slice() + fn dangling(t: Self::Meta) -> NonNull { + let slice = slice_from_raw_parts_mut(NonNull::::dangling().as_ptr(), t.len); + unsafe { NonNull::new_unchecked(slice) } + } + fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self { + slice_from_raw_parts_mut(thin.cast(), t.len) } } #[doc(hidden)] @@ -229,33 +214,92 @@ impl Type for str { const METATYPE: MetaType = MetaType::Slice; type Meta = Slice; #[inline] - fn meta(&self) -> Self::Meta { - assert_eq!( - (mem::size_of_val(self), mem::align_of_val(self)), - (self.len(), 1) - ); - Slice { len: self.len() } + fn meta(self: *const Self) -> Self::Meta { + let self_ = unsafe { &*self }; // https://github.com/rust-lang/rfcs/issues/2017 + assert_eq!((size_of_val(self_), align_of_val(self_)), (self_.len(), 1)); + Slice { len: self_.len() } } #[inline] - fn data(&self) -> *const () { - self.as_ptr() as *const () + fn data(self: *const Self) -> *const () { + self as *const () } #[inline] - fn data_mut(&mut self) -> *mut () { - unsafe { self.as_bytes_mut() }.as_mut_ptr() as *mut () + fn data_mut(self: *mut Self) -> *mut () { + self as *mut () } - unsafe fn uninitialized_box(t: Self::Meta) -> Box { - let mut backing = Vec::::with_capacity(t.len); - backing.set_len(t.len); - String::from_utf8_unchecked(backing).into_boxed_str() + fn dangling(t: Self::Meta) -> NonNull { + let bytes: *mut [u8] = <[u8]>::dangling(t).as_ptr(); + unsafe { NonNull::new_unchecked(bytes as *mut Self) } } + fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self { + <[u8]>::fatten(thin, t) as *mut Self + } +} + +unsafe fn transmute_coerce(a: A) -> B { + assert_eq!( + (size_of::(), align_of::()), + (size_of::(), align_of::()), + "can't transmute_coerce {} to {} as sizes/alignments differ", + type_name::(), + type_name::() + ); + let b = transmute_copy(&a); + forget(a); + b +} + +/// Convert from one type parameter to another, where they are the same type. +/// Panics with an explanatory message if the types differ. +/// +/// In almost all circumstances this isn't needed, but it can be very useful in +/// cases like [rust-lang/rust#50318](https://github.com/rust-lang/rust/issues/50318). +pub fn type_coerce(a: A) -> B { + try_type_coerce(a) + .unwrap_or_else(|| panic!("can't coerce {} to {}", type_name::(), type_name::())) +} + +/// Convert from one type parameter to another, where they are the same type. +/// Returns `None` if the types differ. +/// +/// In almost all circumstances this isn't needed, but it can be very useful in +/// cases like [rust-lang/rust#50318](https://github.com/rust-lang/rust/issues/50318). +pub fn try_type_coerce(a: A) -> Option { + trait Eq { + fn eq(self) -> Option; + } + + struct Foo(A, PhantomData); + + impl Eq for Foo { + default fn eq(self) -> Option { + None + } + } + impl Eq for Foo { + fn eq(self) -> Option { + Some(self.0) + } + } + + Foo::(a, PhantomData).eq() +} + +/// Gets an identifier which is globally unique to the specified type. This +/// function will return the same value for a type regardless of whichever crate +/// it is invoked in. +pub fn type_id() -> u64 { + let type_id = TypeId::of::(); + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + type_id.hash(&mut hasher); + hasher.finish() } #[cfg(test)] mod tests { #![allow(clippy::cast_ptr_alignment, clippy::shadow_unrelated)] - use super::{MetaType, Type}; - use std::{any, mem, ptr}; + use super::{type_coerce, MetaType, Slice, TraitObject, Type}; + use std::{any, ptr::NonNull}; #[test] fn abc() { @@ -265,19 +309,34 @@ mod tests { let a: Box = a; assert_eq!(Type::meta_type(&*a), MetaType::TraitObject); assert_eq!(Type::meta_type(&a), MetaType::Concrete); - let meta = Type::meta(&*a); // : TraitObject - let mut b: Box = unsafe { Type::uninitialized_box(meta) }; - assert_eq!(mem::size_of_val(&*b), mem::size_of::()); - unsafe { ptr::write(&mut *b as *mut dyn any::Any as *mut usize, 456_usize) }; - let x: usize = *Box::::downcast(b).unwrap(); - assert_eq!(x, 456); + let meta: TraitObject = type_coerce(Type::meta(&*a)); + let dangling = ::dangling(type_coerce(meta)); + let _fat = ::fatten(dangling.as_ptr() as *mut (), type_coerce(meta)); + let mut x: usize = 0; + let x_ptr: *mut usize = &mut x; + let mut x_ptr: NonNull = NonNull::new(::fatten( + x_ptr as *mut (), + type_coerce(meta), + )) + .unwrap(); + let x_ref: &mut dyn any::Any = unsafe { x_ptr.as_mut() }; + let x_ref: &mut usize = x_ref.downcast_mut().unwrap(); + *x_ref = 123; + assert_eq!(x, 123); + let a: &[usize] = &[1, 2, 3]; assert_eq!(Type::meta_type(a), MetaType::Slice); + let dangling = <[String] as Type>::dangling(Slice { len: 100 }); + let _fat = <[String] as Type>::fatten(dangling.as_ptr() as *mut (), Slice { len: 100 }); + let a: Box<[usize]> = vec![1_usize, 2, 3].into_boxed_slice(); assert_eq!(Type::meta_type(&*a), MetaType::Slice); assert_eq!(Type::meta_type(&a), MetaType::Concrete); + let a: &str = "abc"; assert_eq!(Type::meta_type(a), MetaType::Slice); assert_eq!(Type::meta_type(&a), MetaType::Concrete); + let dangling = ::dangling(Slice { len: 100 }); + let _fat = ::fatten(dangling.as_ptr() as *mut (), Slice { len: 100 }); } }