From 8a6eedd4997f070ec175e4b1b41825e60cf8dba5 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 10 Aug 2023 18:04:34 +0000 Subject: [PATCH] refactor: dyn ObjValue --- --- a/Cargo.lock +++ b/Cargo.lock @@ -219,6 +219,17 @@ ] [[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -346,6 +357,7 @@ "anyhow", "async-trait", "bincode", + "derivative", "hashbrown 0.13.2", "jrsonnet-gcmodule", "jrsonnet-interner", --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -58,3 +58,4 @@ async-trait = { version = "0.1.60", optional = true } # Bigint num-bigint = { version = "0.4.3", features = ["serde"], optional = true } +derivative = "2.2.0" --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -116,7 +116,10 @@ } } -pub struct GcHashMap(pub HashMap>); +#[derive(Debug)] +pub struct GcHashMap( + pub HashMap> +); impl GcHashMap { pub fn new() -> Self { Self(HashMap::default()) --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,4 +1,5 @@ use std::{ + any::Any, cell::RefCell, fmt::Debug, hash::{Hash, Hasher}, @@ -94,11 +95,49 @@ use ordering::*; +// 0 - add +// 12 - visibility +#[derive(Clone, Copy)] +pub struct ObjFieldFlags(u8); +impl ObjFieldFlags { + fn new(add: bool, visibility: Visibility) -> Self { + let mut v = 0; + if add { + v |= 1; + } + v |= match visibility { + Visibility::Normal => 0b000, + Visibility::Hidden => 0b010, + Visibility::Unhide => 0b100, + }; + Self(v) + } + pub fn add(&self) -> bool { + self.0 & 1 != 0 + } + pub fn visibility(&self) -> Visibility { + match (self.0 & 0b110) >> 1 { + 0b00 => Visibility::Normal, + 0b01 => Visibility::Hidden, + 0b10 => Visibility::Unhide, + _ => unreachable!(), + } + } +} +impl Debug for ObjFieldFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ObjFieldFlags") + .field("add", &self.add()) + .field("visibility", &self.visibility()) + .finish() + } +} + #[allow(clippy::module_name_repetitions)] #[derive(Debug, Trace)] pub struct ObjMember { - pub add: bool, - pub visibility: Visibility, + #[trace(skip)] + flags: ObjFieldFlags, original_index: FieldIndex, pub invoke: MaybeUnbound, pub location: Option, @@ -121,18 +160,54 @@ #[allow(clippy::module_name_repetitions)] #[derive(Trace)] #[trace(tracking(force))] -pub struct ObjValueInternals { +pub struct OopObject { sup: Option, - this: Option, - + // this: Option, assertions: Cc>>, assertions_ran: RefCell>, this_entries: Cc>, value_cache: RefCell), CacheValue>>, } +impl Debug for OopObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OopObject") + .field("sup", &self.sup) + // .field("assertions", &self.assertions) + // .field("assertions_ran", &self.assertions_ran) + .field("this_entries", &self.this_entries) + // .field("value_cache", &self.value_cache) + .finish() + } +} + +type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a; + +pub trait ObjectLike: Trace + Any + Debug { + fn extend_from(&self, sup: ObjValue) -> ObjValue; + /// When using standalone super in object, `this.super_obj.with_this(this)` is executed + fn with_this(&self, me: ObjValue, this: ObjValue) -> ObjValue { + ObjValue::new(ThisOverride { inner: me, this }) + } + fn this(&self) -> Option { + None + } + fn len(&self) -> usize; + fn is_empty(&self) -> bool; + // If callback returns false, iteration stops + fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool; + + fn has_field_include_hidden(&self, name: IStr) -> bool; + fn has_field(&self, name: IStr) -> bool; + + fn get_for(&self, key: IStr, this: ObjValue) -> Result>; + fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result>; + fn field_visibility(&self, field: IStr) -> Option; + + fn run_assertions_raw(&self, this: ObjValue) -> Result<()>; +} #[derive(Clone, Trace)] -pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak); +pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak>); impl PartialEq for WeakObjValue { fn eq(&self, other: &Self) -> bool { @@ -150,138 +225,259 @@ } #[allow(clippy::module_name_repetitions)] -#[derive(Clone, Trace)] -pub struct ObjValue(pub(crate) Cc); -impl Debug for ObjValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(super_obj) = self.0.sup.as_ref() { - if f.alternate() { - write!(f, "{super_obj:#?}")?; - } else { - write!(f, "{super_obj:?}")?; - } - write!(f, " + ")?; - } - let mut debug = f.debug_struct("ObjValue"); - for (name, member) in self.0.this_entries.iter() { - debug.field(name, member); - } - debug.finish_non_exhaustive() +#[derive(Clone, Trace, Debug)] +pub struct ObjValue(pub(crate) Cc>); + +#[derive(Debug, Trace)] +struct EmptyObject; +impl ObjectLike for EmptyObject { + fn extend_from(&self, sup: ObjValue) -> ObjValue { + // obj + {} == obj + sup + } + + fn this(&self) -> Option { + None + } + + fn len(&self) -> usize { + 0 } + + fn is_empty(&self) -> bool { + true + } + + fn enum_fields(&self, _depth: SuperDepth, _handler: &mut EnumFieldsHandler<'_>) -> bool { + false + } + + fn has_field_include_hidden(&self, _name: IStr) -> bool { + false + } + + fn has_field(&self, _name: IStr) -> bool { + false + } + + fn get_for(&self, _key: IStr, _this: ObjValue) -> Result> { + Ok(None) + } + fn get_for_uncached(&self, _key: IStr, _this: ObjValue) -> Result> { + Ok(None) + } + + fn run_assertions_raw(&self, _this: ObjValue) -> Result<()> { + Ok(()) + } + + fn field_visibility(&self, _field: IStr) -> Option { + None + } } +#[derive(Trace, Debug)] +struct ThisOverride { + inner: ObjValue, + this: ObjValue, +} +impl ObjectLike for ThisOverride { + fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue { + ObjValue::new(ThisOverride { + inner: self.inner.clone(), + this, + }) + } + + fn extend_from(&self, sup: ObjValue) -> ObjValue { + self.inner.extend_from(sup).with_this(self.this.clone()) + } + + fn this(&self) -> Option { + Some(self.this.clone()) + } + + fn len(&self) -> usize { + self.inner.len() + } + + fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { + self.inner.enum_fields(depth, handler) + } + + fn has_field_include_hidden(&self, name: IStr) -> bool { + self.inner.has_field_include_hidden(name) + } + + fn has_field(&self, name: IStr) -> bool { + self.inner.has_field(name) + } + + fn get_for(&self, key: IStr, this: ObjValue) -> Result> { + self.inner.get_for(key, this) + } + + fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result> { + self.inner.get_raw(key, this) + } + + fn field_visibility(&self, field: IStr) -> Option { + self.inner.field_visibility(field) + } + + fn run_assertions_raw(&self, this: ObjValue) -> Result<()> { + self.inner.run_assertions_raw(this) + } +} + impl ObjValue { - pub fn new( - sup: Option, - this_entries: Cc>, - assertions: Cc>>, - ) -> Self { - Self(Cc::new(ObjValueInternals { - sup, - this: None, - assertions, - assertions_ran: RefCell::new(GcHashSet::new()), - this_entries, - value_cache: RefCell::new(GcHashMap::new()), - })) + pub fn new(v: impl ObjectLike) -> Self { + Self(Cc::new(tb!(v))) } pub fn new_empty() -> Self { - Self::new(None, Cc::new(GcHashMap::new()), Cc::new(Vec::new())) + Self::new(EmptyObject) } pub fn builder() -> ObjValueBuilder { ObjValueBuilder::new() } pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder { ObjValueBuilder::with_capacity(capacity) - } - #[must_use] - pub fn extend_from(&self, sup: Self) -> Self { - match &self.0.sup { - None => Self::new( - Some(sup), - self.0.this_entries.clone(), - self.0.assertions.clone(), - ), - Some(v) => Self::new( - Some(v.extend_from(sup)), - self.0.this_entries.clone(), - self.0.assertions.clone(), - ), - } } pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self { - let mut new = GcHashMap::with_capacity(1); - new.insert(key, value); - Self::new(Some(self), Cc::new(new), Cc::new(Vec::new())) + // let mut new = GcHashMap::with_capacity(1); + // new.insert(key, value); + // Self::new(Some(self), Cc::new(new), Cc::new(Vec::new())) + todo!() } pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder> { ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default()) } #[must_use] + pub fn extend_from(&self, sup: Self) -> Self { + self.0.extend_from(sup) + } + #[must_use] pub fn with_this(&self, this: Self) -> Self { - Self(Cc::new(ObjValueInternals { - sup: self.0.sup.clone(), - assertions: self.0.assertions.clone(), - assertions_ran: RefCell::new(GcHashSet::new()), - this: Some(this), - this_entries: self.0.this_entries.clone(), - value_cache: RefCell::new(GcHashMap::new()), - })) + self.0.with_this(self.clone(), this) } - pub fn len(&self) -> usize { - self.fields_visibility() - .into_iter() - .filter(|(_, (visible, _))| *visible) - .count() + self.0.len() + } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { + self.0.enum_fields(depth, handler) } - pub fn is_empty(&self) -> bool { - if !self.0.this_entries.is_empty() { - return false; + pub fn has_field_include_hidden(&self, name: IStr) -> bool { + self.0.has_field_include_hidden(name) + } + pub fn has_field(&self, name: IStr) -> bool { + self.0.has_field(name) + } + pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool { + if include_hidden { + self.has_field_include_hidden(name) + } else { + self.has_field(name) } - self.0.sup.as_ref().map_or(true, Self::is_empty) } - /// Run callback for every field found in object - /// - /// Returns true if ended prematurely - pub(crate) fn enum_fields( + pub fn get(&self, key: IStr) -> Result> { + self.run_assertions()?; + self.get_for(key, self.0.this().unwrap_or(self.clone())) + } + + pub fn get_for(&self, key: IStr, this: ObjValue) -> Result> { + self.0.get_for(key, this) + } + + fn get_raw(&self, key: IStr, this: ObjValue) -> Result> { + self.0.get_for_uncached(key, this) + } + + fn field_visibility(&self, field: IStr) -> Option { + self.0.field_visibility(field) + } + + pub fn run_assertions(&self) -> Result<()> { + // FIXME: Should it use `self.0.this()` in case of standalone super? + self.run_assertions_raw(self.clone()) + } + fn run_assertions_raw(&self, this: ObjValue) -> Result<()> { + self.0.run_assertions_raw(this) + } + + pub fn iter( &self, - depth: SuperDepth, - handler: &mut impl FnMut(SuperDepth, &IStr, &ObjMember) -> bool, - ) -> bool { - if let Some(s) = &self.0.sup { - if s.enum_fields(depth.deeper(), handler) { - return true; - } + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> impl Iterator)> + '_ { + let fields = self.fields( + #[cfg(feature = "exp-preserve-order")] + preserve_order, + ); + fields.into_iter().map(|field| { + ( + field.clone(), + self.get(field) + .map(|opt| opt.expect("iterating over keys, field exists")), + ) + }) + } + pub fn get_lazy(&self, key: IStr) -> Option> { + #[derive(Trace)] + struct ThunkGet { + obj: ObjValue, + key: IStr, } - for (name, member) in self.0.this_entries.iter() { - if handler(depth, name, member) { - return true; + impl ThunkValue for ThunkGet { + type Output = Val; + + fn get(self: Box) -> Result { + Ok(self.obj.get(self.key)?.expect("field exists")) } } - false + + if !self.has_field_ex(key.clone(), true) { + return None; + } + Some(Thunk::new(ThunkGet { + obj: self.clone(), + key, + })) } - - pub fn fields_visibility(&self) -> FxHashMap { + pub fn ptr_eq(a: &Self, b: &Self) -> bool { + Cc::ptr_eq(&a.0, &b.0) + } + pub fn downgrade(self) -> WeakObjValue { + WeakObjValue(self.0.downgrade()) + } + fn fields_visibility(&self) -> FxHashMap { let mut out = FxHashMap::default(); - self.enum_fields(SuperDepth::default(), &mut |depth, name, member| { - let new_sort_key = FieldSortKey::new(depth, member.original_index); - let entry = out.entry(name.clone()); - let (visible, _) = entry.or_insert((true, new_sort_key)); - match member.visibility { - Visibility::Normal => {} - Visibility::Hidden => { - *visible = false; - } - Visibility::Unhide => { - *visible = true; - } - }; - false - }); + self.enum_fields( + SuperDepth::default(), + &mut |depth, index, name, visibility| { + let new_sort_key = FieldSortKey::new(depth, index); + let entry = out.entry(name.clone()); + let (visible, _) = entry.or_insert((true, new_sort_key)); + match visibility { + Visibility::Normal => {} + Visibility::Hidden => { + *visible = false; + } + Visibility::Unhide => { + *visible = true; + } + }; + false + }, + ); out } pub fn fields_ex( @@ -333,95 +529,122 @@ preserve_order, ) } +} - pub fn field_visibility(&self, name: IStr) -> Option { - if let Some(m) = self.0.this_entries.get(&name) { - Some(match &m.visibility { - Visibility::Normal => self - .0 - .sup - .as_ref() - .and_then(|super_obj| super_obj.field_visibility(name)) - .unwrap_or(Visibility::Normal), - v => *v, - }) - } else if let Some(super_obj) = &self.0.sup { - super_obj.field_visibility(name) - } else { - None +impl OopObject { + pub fn new( + sup: Option, + this_entries: Cc>, + assertions: Cc>>, + ) -> Self { + Self { + sup, + // this: None, + assertions, + assertions_ran: RefCell::new(GcHashSet::new()), + this_entries, + value_cache: RefCell::new(GcHashMap::new()), } } - fn has_field_include_hidden(&self, name: IStr) -> bool { - if self.0.this_entries.contains_key(&name) { - true - } else if let Some(super_obj) = &self.0.sup { - super_obj.has_field_include_hidden(name) - } else { - false - } + fn evaluate_this(&self, v: &ObjMember, real_this: ObjValue) -> Result { + v.invoke.evaluate(self.sup.clone(), Some(real_this)) } - pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool { - if include_hidden { - self.has_field_include_hidden(name) - } else { - self.has_field(name) - } + // FIXME: Duplication between ObjValue and OopObject + fn fields_visibility(&self) -> FxHashMap { + let mut out = FxHashMap::default(); + self.enum_fields( + SuperDepth::default(), + &mut |depth, index, name, visibility| { + let new_sort_key = FieldSortKey::new(depth, index); + let entry = out.entry(name.clone()); + let (visible, _) = entry.or_insert((true, new_sort_key)); + match visibility { + Visibility::Normal => {} + Visibility::Hidden => { + *visible = false; + } + Visibility::Unhide => { + *visible = true; + } + }; + false + }, + ); + out } - pub fn has_field(&self, name: IStr) -> bool { - self.field_visibility(name) - .map_or(false, |v| v.is_visible()) +} + +impl ObjectLike for OopObject { + fn extend_from(&self, sup: ObjValue) -> ObjValue { + ObjValue::new(match &self.sup { + None => Self::new( + Some(sup), + self.this_entries.clone(), + self.assertions.clone(), + ), + Some(v) => Self::new( + Some(v.extend_from(sup)), + self.this_entries.clone(), + self.assertions.clone(), + ), + }) } - pub fn iter( - &self, - #[cfg(feature = "exp-preserve-order")] preserve_order: bool, - ) -> impl Iterator)> + '_ { - let fields = self.fields( - #[cfg(feature = "exp-preserve-order")] - preserve_order, - ); - fields.into_iter().map(|field| { - ( - field.clone(), - self.get(field) - .map(|opt| opt.expect("iterating over keys, field exists")), - ) - }) + fn len(&self) -> usize { + self.fields_visibility() + .into_iter() + .filter(|(_, (visible, _))| *visible) + .count() } - pub fn get_lazy(&self, key: IStr) -> Option> { - #[derive(Trace)] - struct ThunkGet { - obj: ObjValue, - key: IStr, + + fn is_empty(&self) -> bool { + if !self.this_entries.is_empty() { + return false; } - impl ThunkValue for ThunkGet { - type Output = Val; + self.sup.as_ref().map_or(true, ObjValue::is_empty) + } - fn get(self: Box) -> Result { - Ok(self.obj.get(self.key)?.expect("field exists")) + /// Run callback for every field found in object + /// + /// Returns true if ended prematurely + fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { + if let Some(s) = &self.sup { + if s.enum_fields(depth.deeper(), handler) { + return true; + } + } + for (name, member) in self.this_entries.iter() { + if handler( + depth, + member.original_index, + name.clone(), + member.flags.visibility(), + ) { + return true; } } + false + } - if !self.has_field_ex(key.clone(), true) { - return None; + fn has_field_include_hidden(&self, name: IStr) -> bool { + if self.this_entries.contains_key(&name) { + true + } else if let Some(super_obj) = &self.sup { + super_obj.has_field_include_hidden(name) + } else { + false } - Some(Thunk::new(ThunkGet { - obj: self.clone(), - key, - })) } - pub fn get(&self, key: IStr) -> Result> { - self.get_for(key, self.0.this.clone().unwrap_or_else(|| self.clone())) + fn has_field(&self, name: IStr) -> bool { + self.field_visibility(name) + .map_or(false, |v| v.is_visible()) } - pub fn get_for(&self, key: IStr, this: Self) -> Result> { - self.run_assertions()?; - let cache_key = ( - key.clone(), - (!ObjValue::ptr_eq(&this, self)).then(|| this.clone().downgrade()), - ); - if let Some(v) = self.0.value_cache.borrow().get(&cache_key) { + + fn get_for(&self, key: IStr, this: ObjValue) -> Result> { + let cache_key = (key.clone(), Some(this.clone().downgrade())); + if let Some(v) = self.value_cache.borrow().get(&cache_key) { return Ok(match v { CacheValue::Cached(v) => Some(v.clone()), CacheValue::NotFound => None, @@ -429,18 +652,16 @@ CacheValue::Errored(e) => return Err(e.clone()), }); } - self.0 - .value_cache + self.value_cache .borrow_mut() .insert(cache_key.clone(), CacheValue::Pending); - let value = self.get_raw(key, this).map_err(|e| { - self.0 - .value_cache + let value = self.get_for_uncached(key, this).map_err(|e| { + self.value_cache .borrow_mut() .insert(cache_key.clone(), CacheValue::Errored(e.clone())); e })?; - self.0.value_cache.borrow_mut().insert( + self.value_cache.borrow_mut().insert( cache_key, value .as_ref() @@ -448,13 +669,12 @@ ); Ok(value) } - - fn get_raw(&self, key: IStr, real_this: Self) -> Result> { - match (self.0.this_entries.get(&key), &self.0.sup) { + fn get_for_uncached(&self, key: IStr, real_this: ObjValue) -> Result> { + match (self.this_entries.get(&key), &self.sup) { (Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)), (Some(k), Some(super_obj)) => { let our = self.evaluate_this(k, real_this.clone())?; - if k.add { + if k.flags.add() { super_obj .get_raw(key, real_this)? .map_or(Ok(Some(our.clone())), |v| { @@ -468,40 +688,43 @@ (None, None) => Ok(None), } } - fn evaluate_this(&self, v: &ObjMember, real_this: Self) -> Result { - v.invoke.evaluate(self.0.sup.clone(), Some(real_this)) + fn field_visibility(&self, name: IStr) -> Option { + if let Some(m) = self.this_entries.get(&name) { + Some(match &m.flags.visibility() { + Visibility::Normal => self + .sup + .as_ref() + .and_then(|super_obj| super_obj.field_visibility(name)) + .unwrap_or(Visibility::Normal), + v => *v, + }) + } else if let Some(super_obj) = &self.sup { + super_obj.field_visibility(name) + } else { + None + } } - fn run_assertions_raw(&self, real_this: &Self) -> Result<()> { - if self.0.assertions.is_empty() { - if let Some(super_obj) = &self.0.sup { + fn run_assertions_raw(&self, real_this: ObjValue) -> Result<()> { + if self.assertions.is_empty() { + if let Some(super_obj) = &self.sup { super_obj.run_assertions_raw(real_this)?; } return Ok(()); } - if self.0.assertions_ran.borrow_mut().insert(real_this.clone()) { - for assertion in self.0.assertions.iter() { - if let Err(e) = assertion.run(self.0.sup.clone(), Some(real_this.clone())) { - self.0.assertions_ran.borrow_mut().remove(real_this); + if self.assertions_ran.borrow_mut().insert(real_this.clone()) { + for assertion in self.assertions.iter() { + if let Err(e) = assertion.run(self.sup.clone(), Some(real_this.clone())) { + self.assertions_ran.borrow_mut().remove(&real_this); return Err(e); } } - if let Some(super_obj) = &self.0.sup { + if let Some(super_obj) = &self.sup { super_obj.run_assertions_raw(real_this)?; } } Ok(()) - } - pub fn run_assertions(&self) -> Result<()> { - self.run_assertions_raw(self) - } - - pub fn ptr_eq(a: &Self, b: &Self) -> bool { - Cc::ptr_eq(&a.0, &b.0) } - pub fn downgrade(self) -> WeakObjValue { - WeakObjValue(self.0.downgrade()) - } } impl PartialEq for ObjValue { @@ -556,7 +779,14 @@ } pub fn build(self) -> ObjValue { - ObjValue::new(self.sup, Cc::new(self.map), Cc::new(self.assertions)) + if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() { + return ObjValue::new_empty(); + } + ObjValue::new(OopObject::new( + self.sup, + Cc::new(self.map), + Cc::new(self.assertions), + )) } } impl Default for ObjValueBuilder { @@ -612,8 +842,7 @@ self.kind, self.name, ObjMember { - add: self.add, - visibility: self.visibility, + flags: ObjFieldFlags::new(self.add, self.visibility), original_index: self.original_index, invoke: binding, location: self.location, --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,6 +1,7 @@ use std::{ cell::RefCell, fmt::{self, Debug, Display}, + hash::Hasher, mem::replace, rc::Rc, }; @@ -8,6 +9,7 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_types::ValType; +use rustc_hash::FxHasher; pub use crate::arr::{ArrValue, ArrayLike}; use crate::{ -- gitstuff