Skip to content

Commit

Permalink
feat: add GCell and Orphan types. [no ci]
Browse files Browse the repository at this point in the history
  • Loading branch information
rzvxa committed Apr 15, 2024
1 parent 03548a4 commit 20b2746
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 3 deletions.
156 changes: 156 additions & 0 deletions crates/oxc_ast/src/traverse/cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! Cell type and token for traversing AST.
//!
//! Based on `GhostCell`.
//! All method implementations copied verbatim from original version by paper's authors
//! https://gitlab.mpi-sws.org/FP/ghostcell/-/blob/master/ghostcell/src/lib.rs
//! and `ghost_cell` crate https://docs.rs/ghost-cell .
//!
//! Only difference is that instead of using a lifetime to constrain the life of access tokens,
//! here we provide only an unsafe method `Token::new_unchecked` and the user must maintain
//! the invariant that only one token may be "in play" at same time
//! (see below for exactly what "in play" means).
//!
//! This alteration removes a lifetime, and avoids the unergonomic pattern of all the code that
//! works with a structure containing `GCell`s needing to be within a single closure.

use std::cell::UnsafeCell;

/// Access token for traversing AST.
#[repr(transparent)]
pub struct Token(());

impl Token {
/// Create new access token for traversing AST.
///
/// It is imperative that any code operating on a single AST does not have access to more
/// than 1 token. `GCell` uses this guarantee to make it impossible to obtain a `&mut`
/// reference to any AST node while another reference exists. If more than 1 token is "in play",
/// this guarantee can be broken, and may lead to undefined behavior.
///
/// This function is used internally by `transform`, but probably should not be used elsewhere.
///
/// It is permissable to create multiple tokens which are never used together on the same AST.
/// In practice, this means it is possible to transform multiple ASTs on different threads
/// simultaneously.
///
/// If operating on multiple ASTs together (e.g. concatenating 2 files), then a single token
/// must be used to access all the ASTs involved in the operation NOT 1 token per AST.
///
/// # SAFETY
/// Caller must ensure only a single token is used with any AST at one time.
#[inline]
pub unsafe fn new_unchecked() -> Self {
Self(())
}
}

/// A cell type providing interior mutability, with aliasing rules enforced at compile time.
#[repr(transparent)]
pub struct GCell<T: ?Sized> {
value: UnsafeCell<T>,
}

#[allow(dead_code)]
impl<T> GCell<T> {
pub const fn new(value: T) -> Self {
GCell {
value: UnsafeCell::new(value),
}
}

pub fn into_inner(self) -> T {
self.value.into_inner()
}
}

#[allow(dead_code, unused_variables)]
impl<T: ?Sized> GCell<T> {
#[inline]
pub fn borrow<'a>(&'a self, tk: &'a Token) -> &'a T {
unsafe { &*self.value.get() }
}

#[inline]
pub fn borrow_mut<'a>(&'a self, tk: &'a mut Token) -> &'a mut T {
unsafe { &mut *self.value.get() }
}

#[inline]
pub const fn as_ptr(&self) -> *mut T {
self.value.get()
}

#[inline]
pub fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.value.get() }
}

#[inline]
pub fn from_mut(t: &mut T) -> &mut Self {
unsafe { &mut *(t as *mut T as *mut Self) }
}
}

#[allow(dead_code)]
impl<T> GCell<[T]> {
#[inline]
pub fn as_slice_of_cells(&self) -> &[GCell<T>] {
unsafe { &*(self as *const GCell<[T]> as *const [GCell<T>]) }
}
}

#[allow(dead_code)]
impl<T> GCell<T> {
#[inline]
pub fn replace(&self, value: T, tk: &mut Token) -> T {
std::mem::replace(self.borrow_mut(tk), value)
}

#[inline]
pub fn take(&self, tk: &mut Token) -> T
where
T: Default,
{
self.replace(T::default(), tk)
}
}

#[allow(dead_code)]
impl<T: Clone> GCell<T> {
#[inline]
pub fn clone(&self, tk: &Token) -> Self {
GCell::new(self.borrow(tk).clone())
}
}

impl<T: Default> Default for GCell<T> {
#[inline]
fn default() -> Self {
Self::new(T::default())
}
}

impl<T: ?Sized> AsMut<T> for GCell<T> {
#[inline]
fn as_mut(&mut self) -> &mut T {
self.get_mut()
}
}

impl<T> From<T> for GCell<T> {
fn from(t: T) -> Self {
GCell::new(t)
}
}

// SAFETY: `GhostCell` is `Send` + `Sync`, so `GCell` can be too
unsafe impl<T: ?Sized + Send> Send for GCell<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for GCell<T> {}

/// Type alias for a shared ref to a `GCell`.
/// This is the interior-mutable equivalent to `oxc_allocator::Box`.
pub type SharedBox<'a, T> = &'a GCell<T>;

/// Type alias for a shared Vec
pub type SharedVec<'a, T> = oxc_allocator::Vec<'a, GCell<T>>;

7 changes: 6 additions & 1 deletion crates/oxc_ast/src/traverse/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
mod cell;
mod orphan;

pub use orphan::Orphan;

/// This trait is only for checking that we didn't forgot to add `ast_node` attribute to any
/// essential types, Would get removed after cleaning things up.
/// of essential types, Would get removed after cleaning things up.
pub trait TraversableTest {
fn does_support_traversable() {
// Yes, Yes it does my friend!
Expand Down
38 changes: 38 additions & 0 deletions crates/oxc_ast/src/traverse/orphan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::ops::Deref;

/// Wrapper for AST nodes which have been disconnected from the AST.
///
/// This type is central to preventing a node from being attached to the AST in multiple places.
///
/// `Orphan` cannot be `Copy` or `Clone`, or it would allow creating a duplicate ref to the
/// contained node. The original `Orphan` could be attached to the AST, and then the copy
/// could also be attached to the AST elsewhere.
#[repr(transparent)]
pub struct Orphan<T>(T);

impl<T> Orphan<T> {
/// Wrap node to indicate it's disconnected from AST.
/// SAFETY: Caller must ensure that `node` is not attached to the AST.
#[inline]
pub unsafe fn new(node: T) -> Self {
Self(node)
}

/// Unwrap node from `Orphan<T>`.
/// This should only be done before inserting it into the AST.
/// Not unsafe as there is nothing bad you can do with an un-orphaned AST node.
/// No APIs are provided to attach nodes to the AST, unless they're wrapped in `Orphan<T>`.
#[inline]
pub fn inner(self) -> T {
self.0
}
}

impl<T> Deref for Orphan<T> {
type Target = T;

#[inline]
fn deref(&self) -> &T {
&self.0
}
}
5 changes: 3 additions & 2 deletions crates/oxc_macros/src/ast_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ fn modify_enum(item: &mut ItemEnum) -> NodeData {
}

fn validate_struct_attributes<'a>(mut attrs: slice::Iter<'a, Attribute>) {
// make sure that no structure derives Clone/Copy trait.
// TODO: It might fail if there is a manual Clone/Copy trait implemented for the struct.
// make sure that no structure derives Clone/Copy traits.
// TODO: It will fail if there is a manual Clone/Copy traits implemented for the struct.
// Negative traits (!Copy and !Clone) are nightly so I'm not sure how we can fully enforce it.
assert!(!attrs.any(|attr| {
let args = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated);
attr.path().is_ident("derive")
Expand Down

0 comments on commit 20b2746

Please sign in to comment.