use alloc::{boxed::Box, rc::Rc, vec::Vec};
use core::{
any::TypeId,
fmt,
ptr::{DynMetadata, Pointee},
};
use super::{Attribute, AttributeRef, AttributeRegistration};
use crate::{
Context, UnsafeIntrusiveEntityRef, attributes::AttrParser, interner, parse, traits::TraitInfo,
};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AttributeName(Rc<AttributeInfo>);
struct AttributeInfo {
dialect: interner::Symbol,
name: interner::Symbol,
type_id: TypeId,
traits: Box<[TraitInfo]>,
parse_assembly: Option<ParseAssemblyFn>,
alloc_uninit: AllocUninit,
create_default: fn(&Rc<Context>) -> AttributeRef,
}
unsafe fn alloc_uninit<T: AttributeRegistration>(context: Rc<Context>) -> *mut dyn Attribute {
let uninit = context.alloc_uninit_tracked::<T>();
let assumed_init = unsafe { UnsafeIntrusiveEntityRef::assume_init(uninit) };
UnsafeIntrusiveEntityRef::into_raw(assumed_init.as_attribute_ref()).cast_mut()
}
fn create_default<T: AttributeRegistration>(context: &Rc<Context>) -> AttributeRef {
<T as AttributeRegistration>::create_default(context).as_attribute_ref()
}
type AllocUninit = unsafe fn(context: Rc<Context>) -> *mut dyn Attribute;
pub type ParseAssemblyFn = fn(&mut dyn parse::Parser<'_>) -> parse::ParseResult<AttributeRef>;
trait MaybeAttrParser {
fn parse_assembly() -> Option<ParseAssemblyFn>;
}
impl<T> MaybeAttrParser for T {
#[inline(always)]
default fn parse_assembly() -> Option<ParseAssemblyFn> {
None
}
}
impl<T: AttributeRegistration + AttrParser> MaybeAttrParser for T {
#[inline(always)]
fn parse_assembly() -> Option<ParseAssemblyFn> {
Some(<T as AttrParser>::parse)
}
}
impl AttributeName {
pub fn new<T>(dialect: interner::Symbol, mut extra_traits: Vec<TraitInfo>) -> Self
where
T: AttributeRegistration,
{
let type_id = TypeId::of::<T>();
let mut traits = Vec::from(<T as AttributeRegistration>::traits());
traits.append(&mut extra_traits);
traits.sort_by_key(|ti| *ti.type_id());
let parse_assembly = <T as MaybeAttrParser>::parse_assembly();
Self(Rc::new(AttributeInfo {
dialect,
name: <T as AttributeRegistration>::name(),
type_id,
traits: traits.into_boxed_slice(),
parse_assembly,
alloc_uninit: alloc_uninit::<T>,
create_default: create_default::<T>,
}))
}
pub fn dialect(&self) -> interner::Symbol {
self.0.dialect
}
pub fn name(&self) -> interner::Symbol {
self.0.name
}
#[inline]
pub fn id(&self) -> &TypeId {
&self.0.type_id
}
#[inline(always)]
pub fn parse_assembly_fn(&self) -> Option<ParseAssemblyFn> {
self.0.parse_assembly
}
#[inline(always)]
pub fn create_default(&self, context: &Rc<Context>) -> AttributeRef {
(self.0.create_default)(context)
}
pub fn dyn_clone(&self, attr: &dyn Attribute) -> AttributeRef {
let context = attr.context_rc();
assert_eq!(attr.name().id(), self.id());
unsafe {
let uninit = (self.0.alloc_uninit)(context);
attr.clone_to_uninit(uninit.cast());
AttributeRef::from_raw(uninit)
}
}
#[inline]
pub fn is<T: 'static>(&self) -> bool {
TypeId::of::<T>() == self.0.type_id
}
pub fn implements<Trait>(&self) -> bool
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
let type_id = TypeId::of::<Trait>();
self.implements_trait_id(&type_id)
}
pub fn implements_trait_id(&self, trait_id: &TypeId) -> bool {
self.0.traits.binary_search_by(|ti| ti.type_id().cmp(trait_id)).is_ok()
}
#[inline]
pub(super) fn downcast_ref<T: 'static>(&self, ptr: *const ()) -> Option<&T> {
if self.is::<T>() {
Some(unsafe { self.downcast_ref_unchecked(ptr) })
} else {
None
}
}
#[inline(always)]
unsafe fn downcast_ref_unchecked<T: 'static>(&self, ptr: *const ()) -> &T {
unsafe { &*core::ptr::from_raw_parts(ptr.cast::<T>(), ()) }
}
#[inline]
pub(super) fn downcast_mut<T: 'static>(&mut self, ptr: *mut ()) -> Option<&mut T> {
if self.is::<T>() {
Some(unsafe { self.downcast_mut_unchecked(ptr) })
} else {
None
}
}
#[inline(always)]
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self, ptr: *mut ()) -> &mut T {
unsafe { &mut *core::ptr::from_raw_parts_mut(ptr.cast::<T>(), ()) }
}
pub(super) fn upcast<Trait>(&self, ptr: *const ()) -> Option<&Trait>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
let metadata = self
.get::<Trait>()
.map(|trait_impl| unsafe { trait_impl.metadata_unchecked::<Trait>() })?;
Some(unsafe { &*core::ptr::from_raw_parts(ptr, metadata) })
}
pub(super) fn upcast_raw<Trait>(&self, ptr: *const ()) -> Option<*const Trait>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
let metadata = self
.get::<Trait>()
.map(|trait_impl| unsafe { trait_impl.metadata_unchecked::<Trait>() })?;
Some(core::ptr::from_raw_parts(ptr, metadata))
}
pub(super) fn upcast_mut<Trait>(&mut self, ptr: *mut ()) -> Option<&mut Trait>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
let metadata = self
.get::<Trait>()
.map(|trait_impl| unsafe { trait_impl.metadata_unchecked::<Trait>() })?;
Some(unsafe { &mut *core::ptr::from_raw_parts_mut(ptr, metadata) })
}
#[inline]
fn get<Trait: ?Sized + 'static>(&self) -> Option<&TraitInfo> {
let type_id = TypeId::of::<Trait>();
let traits = self.0.traits.as_ref();
traits
.binary_search_by(|ti| ti.type_id().cmp(&type_id))
.ok()
.map(|index| &traits[index])
}
}
impl fmt::Debug for AttributeName {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for AttributeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", &self.dialect(), &self.name())
}
}
impl Eq for AttributeInfo {}
impl PartialEq for AttributeInfo {
fn eq(&self, other: &Self) -> bool {
self.dialect == other.dialect && self.name == other.name && self.type_id == other.type_id
}
}
impl PartialOrd for AttributeInfo {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AttributeInfo {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.dialect
.cmp(&other.dialect)
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.type_id.cmp(&other.type_id))
}
}
impl core::hash::Hash for AttributeInfo {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.dialect.hash(state);
self.name.hash(state);
self.type_id.hash(state);
}
}