1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
//! Helper methods to determine whether a type is `TraitObject`, `Slice` or
//! `Concrete`, and work with them respectively.
//!
//! # Examples
//!
//! ```
//! # use std::{any};
//! # use metatype::*;
//! assert_eq!(usize::METATYPE, MetaType::Concrete);
//! assert_eq!(any::Any::METATYPE, MetaType::TraitObject);
//! assert_eq!(<[u8]>::METATYPE, MetaType::Slice);
//!
//! let a: Box<usize> = Box::new(123);
//! assert_eq!(Type::meta_type(&*a), MetaType::Concrete);
//! let a: Box<dyn any::Any> = a;
//! assert_eq!(Type::meta_type(&*a), MetaType::TraitObject);
//!
//! let a = [123,456];
//! assert_eq!(Type::meta_type(&a), MetaType::Concrete);
//! let a: &[i32] = &a;
//! assert_eq!(Type::meta_type(a), MetaType::Slice);
//!
//! let a: Box<dyn any::Any> = Box::new(123);
//! let meta: TraitObject = type_coerce(Type::meta(&*a));
//! println!("vtable: {:?}", meta.vtable);
//! ```
//!
//! # Note
//!
//! This currently requires Rust nightly for the `raw`, `specialization` and
//! `arbitrary_self_types` features.

#![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_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::{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.
pub trait Type {
	/// Enum describing whether a type is `TraitObject`, `Slice` or `Concrete`.
	const METATYPE: MetaType;
	/// Type of metadata for type.
	type Meta: 'static;
	/// Helper method describing whether a type is `TraitObject`, `Slice` or `Concrete`.
	fn meta_type(self: *const Self) -> MetaType {
		Self::METATYPE
	}
	/// 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 Self) -> *const ();
	/// Retrieve mut pointer to the data
	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<Self>;
	/// 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)]
pub enum MetaType {
	/// Trait object, thus unsized
	TraitObject,
	/// Slice, thus unsized
	Slice,
	/// Sized type
	Concrete,
}

/// Meta data for a trait object
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct TraitObject {
	/// Address of vtable
	pub vtable: &'static (),
}
/// Meta data for a slice
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Slice {
	/// Number of elements in the slice
	pub len: usize,
}
/// Meta data for a concrete, sized type
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Concrete;

impl<T: ?Sized> Type for T {
	#[doc(hidden)]
	default const METATYPE: MetaType = MetaType::TraitObject;
	#[doc(hidden)]
	default type Meta = TraitObject;
	#[inline]
	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 },
		};
		type_coerce(ret)
	}
	#[inline]
	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(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 ()
	}
	#[inline]
	default fn dangling(t: Self::Meta) -> NonNull<Self> {
		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);
			let backing: *const _ = &BACKING;
			backing as *mut ()
		};
		let dangling_unaligned: NonNull<Self> =
			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::<raw::TraitObject, *mut Self>(ret) }
	}
}
#[doc(hidden)]
impl<T: Sized> Type for T {
	const METATYPE: MetaType = MetaType::Concrete;
	type Meta = Concrete;
	#[inline]
	fn meta(self: *const Self) -> Self::Meta {
		Concrete
	}
	#[inline]
	fn data(self: *const Self) -> *const () {
		self as *const ()
	}
	#[inline]
	fn data_mut(self: *mut Self) -> *mut () {
		self as *mut ()
	}
	fn dangling(_t: Self::Meta) -> NonNull<Self> {
		NonNull::dangling()
	}
	fn fatten(thin: *mut (), _t: Self::Meta) -> *mut Self {
		thin.cast()
	}
}
#[doc(hidden)]
impl<T: Sized> Type for [T] {
	const METATYPE: MetaType = MetaType::Slice;
	type Meta = Slice;
	#[inline]
	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_)),
			(size_of::<T>() * self_.len(), align_of::<T>())
		);
		Slice { len: self_.len() }
	}
	#[inline]
	fn data(self: *const Self) -> *const () {
		self as *const ()
	}
	#[inline]
	fn data_mut(self: *mut Self) -> *mut () {
		self as *mut ()
	}
	fn dangling(t: Self::Meta) -> NonNull<Self> {
		let slice = slice_from_raw_parts_mut(NonNull::<T>::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)]
impl Type for str {
	const METATYPE: MetaType = MetaType::Slice;
	type Meta = Slice;
	#[inline]
	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) -> *const () {
		self as *const ()
	}
	#[inline]
	fn data_mut(self: *mut Self) -> *mut () {
		self as *mut ()
	}
	fn dangling(t: Self::Meta) -> NonNull<Self> {
		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, B>(a: A) -> B {
	assert_eq!(
		(size_of::<A>(), align_of::<A>()),
		(size_of::<B>(), align_of::<B>()),
		"can't transmute_coerce {} to {} as sizes/alignments differ",
		type_name::<A>(),
		type_name::<B>()
	);
	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, B>(a: A) -> B {
	try_type_coerce(a)
		.unwrap_or_else(|| panic!("can't coerce {} to {}", type_name::<A>(), type_name::<B>()))
}

/// 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, B>(a: A) -> Option<B> {
	trait Eq<B> {
		fn eq(self) -> Option<B>;
	}

	struct Foo<A, B>(A, PhantomData<fn(B)>);

	impl<A, B> Eq<B> for Foo<A, B> {
		default fn eq(self) -> Option<B> {
			None
		}
	}
	impl<A> Eq<A> for Foo<A, A> {
		fn eq(self) -> Option<A> {
			Some(self.0)
		}
	}

	Foo::<A, B>(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<T: ?Sized + 'static>() -> u64 {
	let type_id = TypeId::of::<T>();
	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::{type_coerce, MetaType, Slice, TraitObject, Type};
	use std::{any, ptr::NonNull};

	#[test]
	fn abc() {
		let a: Box<usize> = Box::new(123);
		assert_eq!(Type::meta_type(&*a), MetaType::Concrete);
		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
		let a: Box<dyn any::Any> = a;
		assert_eq!(Type::meta_type(&*a), MetaType::TraitObject);
		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
		let meta: TraitObject = type_coerce(Type::meta(&*a));
		let dangling = <dyn any::Any as Type>::dangling(type_coerce(meta));
		let _fat = <dyn any::Any as Type>::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<dyn any::Any> = NonNull::new(<dyn any::Any as Type>::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 = <str as Type>::dangling(Slice { len: 100 });
		let _fat = <str as Type>::fatten(dangling.as_ptr() as *mut (), Slice { len: 100 });
	}
}