Rename UncheckedAnyExt, fix Extend, tweak things

The *name* UncheckedAnyExt was ending up visible in docs, but it had
become an increasingly unpleasant name. “Downcast” is suitable, though,
being private, it’s not still not perfect. But there’s no point in
making it public, as people generally can’t implement it because of
coherence rules (I tried).

Plus documentation and style changes.

As for Extend, eh, that should ideally be in a different commit, but
it’s here now, and I’m the only one working on this code base in
general, so I permit myself to be slightly lazy from time to time.
Trouble was Downcast should never have had an Any supertrait, as it was
grossly misleading, leading to type_id giving `dyn Any`’s TypeId rather
than the underlying type’s.
This commit is contained in:
Chris Morgan 2022-02-03 00:40:47 +11:00
parent 9ddafe25f4
commit 93511917c3
3 changed files with 82 additions and 31 deletions

View file

@ -3,6 +3,10 @@
Planned once the dust of 1.0.0-beta.1 settles, since 1.0.0-beta.1 ended up Planned once the dust of 1.0.0-beta.1 settles, since 1.0.0-beta.1 ended up
being bigger than Id earlier intended. being bigger than Id earlier intended.
# 1.0.0-beta.2 (unreleased)
- Fixed the broken `Extend` implementation added in 1.0.0-beta.1.
# 1.0.0-beta.1 (2022-01-25) # 1.0.0-beta.1 (2022-01-25)
- Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its - Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its

View file

@ -1,5 +1,5 @@
use core::fmt; use core::fmt;
use core::any::Any; use core::any::{Any, TypeId};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::boxed::Box; use alloc::boxed::Box;
@ -47,23 +47,61 @@ macro_rules! impl_clone {
} }
} }
#[allow(missing_docs)] // Bogus warning (its not public outside the crate), ☹ /// Methods for downcasting from an `Any`-like trait object.
pub trait UncheckedAnyExt: Any { ///
unsafe fn downcast_ref_unchecked<T: Any>(&self) -> &T; /// This should only be implemented on trait objects for subtraits of `Any`, though you can
unsafe fn downcast_mut_unchecked<T: Any>(&mut self) -> &mut T; /// implement it for other types and itll work fine, so long as your implementation is correct.
unsafe fn downcast_unchecked<T: Any>(self: Box<Self>) -> Box<T>; pub trait Downcast {
/// Gets the `TypeId` of `self`.
fn type_id(&self) -> TypeId;
// Note the bound through these downcast methods is 'static, rather than the inexpressible
// concept of Self-but-as-a-trait (where Self is `dyn Trait`). This is sufficient, exceeding
// TypeIds requirements. Sure, you *can* do CloneAny.downcast_unchecked::<NotClone>() and the
// type system wont protect you, but that doesnt introduce any unsafety: the method is
// already unsafe because you can specify the wrong type, and if this were exposing safe
// downcasting, CloneAny.downcast::<NotClone>() would just return an error, which is just as
// correct.
//
// Now in theory we could also add T: ?Sized, but that doesnt play nicely with the common
// implementation, so Im doing without it.
/// Downcast from `&Any` to `&T`, without checking the type matches.
///
/// # Safety
///
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T;
/// Downcast from `&mut Any` to `&mut T`, without checking the type matches.
///
/// # Safety
///
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T;
/// Downcast from `Box<Any>` to `Box<T>`, without checking the type matches.
///
/// # Safety
///
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
unsafe fn downcast_unchecked<T: 'static>(self: Box<Self>) -> Box<T>;
} }
#[doc(hidden)]
/// A trait for the conversion of an object into a boxed trait object. /// A trait for the conversion of an object into a boxed trait object.
pub trait IntoBox<A: ?Sized + UncheckedAnyExt>: Any { pub trait IntoBox<A: ?Sized + Downcast>: Any {
/// Convert self into the appropriate boxed form. /// Convert self into the appropriate boxed form.
fn into_box(self) -> Box<A>; fn into_box(self) -> Box<A>;
} }
macro_rules! implement { macro_rules! implement {
($base:ident, $(+ $bounds:ident)*) => { ($any_trait:ident $(+ $auto_traits:ident)*) => {
impl UncheckedAnyExt for dyn $base $(+ $bounds)* { impl Downcast for dyn $any_trait $(+ $auto_traits)* {
#[inline]
fn type_id(&self) -> TypeId {
self.type_id()
}
#[inline] #[inline]
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T { unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
&*(self as *const Self as *const T) &*(self as *const Self as *const T)
@ -80,18 +118,18 @@ macro_rules! implement {
} }
} }
impl<T: $base $(+ $bounds)*> IntoBox<dyn $base $(+ $bounds)*> for T { impl<T: $any_trait $(+ $auto_traits)*> IntoBox<dyn $any_trait $(+ $auto_traits)*> for T {
#[inline] #[inline]
fn into_box(self) -> Box<dyn $base $(+ $bounds)*> { fn into_box(self) -> Box<dyn $any_trait $(+ $auto_traits)*> {
Box::new(self) Box::new(self)
} }
} }
} }
} }
implement!(Any,); implement!(Any);
implement!(Any, + Send); implement!(Any + Send);
implement!(Any, + Send + Sync); implement!(Any + Send + Sync);
/// [`Any`], but with cloning. /// [`Any`], but with cloning.
/// ///
@ -99,9 +137,9 @@ implement!(Any, + Send + Sync);
/// See [`core::any`] for more details on `Any` in general. /// See [`core::any`] for more details on `Any` in general.
pub trait CloneAny: Any + CloneToAny { } pub trait CloneAny: Any + CloneToAny { }
impl<T: Any + Clone> CloneAny for T { } impl<T: Any + Clone> CloneAny for T { }
implement!(CloneAny,); implement!(CloneAny);
implement!(CloneAny, + Send); implement!(CloneAny + Send);
implement!(CloneAny, + Send + Sync); implement!(CloneAny + Send + Sync);
impl_clone!(dyn CloneAny); impl_clone!(dyn CloneAny);
impl_clone!(dyn CloneAny + Send); impl_clone!(dyn CloneAny + Send);
impl_clone!(dyn CloneAny + Send + Sync); impl_clone!(dyn CloneAny + Send + Sync);

View file

@ -20,7 +20,7 @@ extern crate alloc;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::boxed::Box; use alloc::boxed::Box;
use any::{UncheckedAnyExt, IntoBox}; use any::{Downcast, IntoBox};
pub use any::CloneAny; pub use any::CloneAny;
#[cfg(all(feature = "std", not(feature = "hashbrown")))] #[cfg(all(feature = "std", not(feature = "hashbrown")))]
@ -108,12 +108,12 @@ pub type RawMap<A> = HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>;
/// ///
/// Values containing non-static references are not permitted. /// Values containing non-static references are not permitted.
#[derive(Debug)] #[derive(Debug)]
pub struct Map<A: ?Sized + UncheckedAnyExt = dyn Any> { pub struct Map<A: ?Sized + Downcast = dyn Any> {
raw: RawMap<A>, raw: RawMap<A>,
} }
// #[derive(Clone)] would want A to implement Clone, but in reality its only Box<A> that can. // #[derive(Clone)] would want A to implement Clone, but in reality its only Box<A> that can.
impl<A: ?Sized + UncheckedAnyExt> Clone for Map<A> where Box<A>: Clone { impl<A: ?Sized + Downcast> Clone for Map<A> where Box<A>: Clone {
#[inline] #[inline]
fn clone(&self) -> Map<A> { fn clone(&self) -> Map<A> {
Map { Map {
@ -129,14 +129,14 @@ impl<A: ?Sized + UncheckedAnyExt> Clone for Map<A> where Box<A>: Clone {
/// Its a bit sad, really. Ah well, I guess this approach will do. /// Its a bit sad, really. Ah well, I guess this approach will do.
pub type AnyMap = Map<dyn Any>; pub type AnyMap = Map<dyn Any>;
impl<A: ?Sized + UncheckedAnyExt> Default for Map<A> { impl<A: ?Sized + Downcast> Default for Map<A> {
#[inline] #[inline]
fn default() -> Map<A> { fn default() -> Map<A> {
Map::new() Map::new()
} }
} }
impl<A: ?Sized + UncheckedAnyExt> Map<A> { impl<A: ?Sized + Downcast> Map<A> {
/// Create an empty collection. /// Create an empty collection.
#[inline] #[inline]
pub fn new() -> Map<A> { pub fn new() -> Map<A> {
@ -336,17 +336,17 @@ impl<A: ?Sized + UncheckedAnyExt> Map<A> {
} }
} }
impl<A: ?Sized + UncheckedAnyExt> Extend<Box<A>> for Map<A> { impl<A: ?Sized + Downcast> Extend<Box<A>> for Map<A> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = Box<A>>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = Box<A>>>(&mut self, iter: T) {
for item in iter { for item in iter {
let _ = self.raw.insert(item.type_id(), item); let _ = self.raw.insert(Downcast::type_id(&*item), item);
} }
} }
} }
/// A view into a single occupied location in an `Map`. /// A view into a single occupied location in an `Map`.
pub struct OccupiedEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> { pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> {
#[cfg(all(feature = "std", not(feature = "hashbrown")))] #[cfg(all(feature = "std", not(feature = "hashbrown")))]
inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box<A>>, inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box<A>>,
#[cfg(feature = "hashbrown")] #[cfg(feature = "hashbrown")]
@ -355,7 +355,7 @@ pub struct OccupiedEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
} }
/// A view into a single empty location in an `Map`. /// A view into a single empty location in an `Map`.
pub struct VacantEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> { pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> {
#[cfg(all(feature = "std", not(feature = "hashbrown")))] #[cfg(all(feature = "std", not(feature = "hashbrown")))]
inner: raw_hash_map::VacantEntry<'a, TypeId, Box<A>>, inner: raw_hash_map::VacantEntry<'a, TypeId, Box<A>>,
#[cfg(feature = "hashbrown")] #[cfg(feature = "hashbrown")]
@ -364,14 +364,14 @@ pub struct VacantEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
} }
/// A view into a single location in an `Map`, which may be vacant or occupied. /// A view into a single location in an `Map`, which may be vacant or occupied.
pub enum Entry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> { pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> {
/// An occupied Entry /// An occupied Entry
Occupied(OccupiedEntry<'a, A, V>), Occupied(OccupiedEntry<'a, A, V>),
/// A vacant Entry /// A vacant Entry
Vacant(VacantEntry<'a, A, V>), Vacant(VacantEntry<'a, A, V>),
} }
impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> Entry<'a, A, V> { impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
/// Ensures a value is in the entry by inserting the default if empty, and returns /// Ensures a value is in the entry by inserting the default if empty, and returns
/// a mutable reference to the value in the entry. /// a mutable reference to the value in the entry.
#[inline] #[inline]
@ -421,7 +421,7 @@ impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> Entry<'a, A, V> {
// insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> (1.59.0) // insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> (1.59.0)
} }
impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> OccupiedEntry<'a, A, V> { impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
/// Gets a reference to the value in the entry /// Gets a reference to the value in the entry
#[inline] #[inline]
pub fn get(&self) -> &V { pub fn get(&self) -> &V {
@ -454,7 +454,7 @@ impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
} }
} }
impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> VacantEntry<'a, A, V> { impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
/// Sets the value of the entry with the VacantEntry's key, /// Sets the value of the entry with the VacantEntry's key,
/// and returns a mutable reference to it /// and returns a mutable reference to it
#[inline] #[inline]
@ -645,4 +645,13 @@ mod tests {
verify_hashing_with(TypeId::of::<&str>()); verify_hashing_with(TypeId::of::<&str>());
verify_hashing_with(TypeId::of::<Vec<u8>>()); verify_hashing_with(TypeId::of::<Vec<u8>>());
} }
#[test]
fn test_extend() {
let mut map = AnyMap::new();
map.extend([Box::new(123) as Box<dyn Any>, Box::new(456), Box::new(true)]);
assert_eq!(map.get(), Some(&456));
assert_eq!(map.get::<bool>(), Some(&true));
assert!(map.get::<Box<dyn Any>>().is_none());
}
} }