use crate::execution::GraphQLLocation;
use crate::schema::Component;
use crate::schema::ComponentOrigin;
use crate::SourceMap;
use apollo_parser::SyntaxNode;
use rowan::TextRange;
use std::collections::hash_map::RandomState;
use std::fmt;
use std::hash::BuildHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::num::NonZeroI64;
use std::sync::atomic;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::OnceLock;
#[derive(serde::Deserialize)]
#[serde(from = "T")]
pub struct Node<T>(triomphe::Arc<NodeInner<T>>);
struct NodeInner<T> {
location: Option<NodeLocation>,
hash_cache: AtomicU64,
node: T,
}
const HASH_NOT_COMPUTED_YET: u64 = 0;
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct NodeLocation {
pub(crate) file_id: FileId,
pub(crate) text_range: TextRange,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FileId {
id: NonZeroI64,
}
impl<T> Node<T> {
#[inline]
pub fn new_parsed(node: T, location: NodeLocation) -> Self {
Self::new_opt_location(node, Some(location))
}
#[inline]
pub fn new(node: T) -> Self {
Self::new_opt_location(node, None)
}
pub(crate) fn new_opt_location(node: T, location: Option<NodeLocation>) -> Self {
Self(triomphe::Arc::new(NodeInner {
location,
node,
hash_cache: AtomicU64::new(HASH_NOT_COMPUTED_YET),
}))
}
pub fn location(&self) -> Option<NodeLocation> {
self.0.location
}
pub fn is_built_in(&self) -> bool {
self.location().map(|l| l.file_id()) == Some(FileId::BUILT_IN)
}
pub fn line_column(&self, sources: &SourceMap) -> Option<GraphQLLocation> {
GraphQLLocation::from_node(sources, self.location())
}
pub fn same_location<U>(&self, node: U) -> Node<U> {
Node::new_opt_location(node, self.0.location)
}
pub fn to_component(&self, origin: ComponentOrigin) -> Component<T> {
Component {
origin,
node: self.clone(),
}
}
pub fn ptr_eq(&self, other: &Self) -> bool {
triomphe::Arc::ptr_eq(&self.0, &other.0)
}
pub fn make_mut(&mut self) -> &mut T
where
T: Clone,
{
let inner = triomphe::Arc::make_mut(&mut self.0);
*inner.hash_cache.get_mut() = HASH_NOT_COMPUTED_YET;
&mut inner.node
}
pub fn get_mut(&mut self) -> Option<&mut T> {
triomphe::Arc::get_mut(&mut self.0).map(|inner| &mut inner.node)
}
}
impl<T> std::ops::Deref for Node<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0.node
}
}
impl<T> Clone for Node<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Default> Default for Node<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: fmt::Debug> fmt::Debug for Node<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(location) = self.location() {
write!(f, "{location:?} ")?
}
self.0.node.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Node<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
T::fmt(self, f)
}
}
impl<T: Eq> Eq for Node<T> {}
impl<T: PartialEq> PartialEq for Node<T> {
fn eq(&self, other: &Self) -> bool {
self.ptr_eq(other) || self.0.node == other.0.node }
}
impl<T: Hash> Hash for Node<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
let hash = self.0.hash_cache.load(Ordering::Relaxed);
if hash != HASH_NOT_COMPUTED_YET {
hash
} else {
hash_slow_path(&self.0)
}
.hash(state)
}
}
#[cold]
#[inline(never)]
fn hash_slow_path<T: Hash>(inner: &NodeInner<T>) -> u64 {
static SHARED_RANDOM: OnceLock<RandomState> = OnceLock::new();
let mut hash = SHARED_RANDOM
.get_or_init(RandomState::new)
.hash_one(&inner.node);
if hash == HASH_NOT_COMPUTED_YET {
hash += 1
}
inner.hash_cache.store(hash, Ordering::Relaxed);
hash
}
impl<T> AsRef<T> for Node<T> {
fn as_ref(&self) -> &T {
self
}
}
impl<T> From<T> for Node<T> {
fn from(node: T) -> Self {
Self::new(node)
}
}
impl<T: Clone> Clone for NodeInner<T> {
fn clone(&self) -> Self {
Self {
location: self.location,
hash_cache: AtomicU64::new(self.hash_cache.load(Ordering::Relaxed)),
node: self.node.clone(),
}
}
}
impl NodeLocation {
pub(crate) fn new(file_id: FileId, node: &'_ SyntaxNode) -> Self {
Self {
file_id,
text_range: node.text_range(),
}
}
pub fn file_id(&self) -> FileId {
self.file_id
}
pub fn offset(&self) -> usize {
self.text_range.start().into()
}
pub fn end_offset(&self) -> usize {
self.text_range.end().into()
}
pub fn node_len(&self) -> usize {
self.text_range.len().into()
}
pub fn recompose(start_of: Option<Self>, end_of: Option<Self>) -> Option<Self> {
match (start_of, end_of) {
(None, None) => None,
(None, single @ Some(_)) | (single @ Some(_), None) => single,
(Some(start), Some(end)) => {
if start.file_id != end.file_id {
return Some(end);
}
Some(NodeLocation {
file_id: start.file_id,
text_range: TextRange::new(start.text_range.start(), end.text_range.end()),
})
}
}
}
}
impl fmt::Debug for NodeLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}..{} @{:?}",
self.offset(),
self.end_offset(),
self.file_id,
)
}
}
impl<T: serde::Serialize> serde::Serialize for Node<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
T::serialize(self, serializer)
}
}
impl fmt::Debug for FileId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
static NEXT: atomic::AtomicI64 = atomic::AtomicI64::new(INITIAL);
static INITIAL: i64 = 1;
impl FileId {
pub const BUILT_IN: Self = Self::const_new(-1);
pub(crate) const NONE: Self = Self::const_new(-2);
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let id = NEXT.fetch_add(1, atomic::Ordering::AcqRel);
Self {
id: NonZeroI64::new(id).unwrap(),
}
}
#[doc(hidden)]
pub fn reset() {
NEXT.store(INITIAL, atomic::Ordering::Release)
}
const fn const_new(id: i64) -> Self {
if let Some(id) = NonZeroI64::new(id) {
Self { id }
} else {
panic!()
}
}
}