-
-
Notifications
You must be signed in to change notification settings - Fork 484
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add GCell and Orphan types. [no ci]
- Loading branch information
Showing
4 changed files
with
203 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>>; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters