use crate::{
AsObject, Py, PyExact, PyObject, PyObjectRef, PyRefExact, PyResult, VirtualMachine,
builtins::{PyBytes, PyInt, PyStr, PyStrInterned, PyStrRef, PyUtf8Str, PyUtf8StrRef},
convert::ToPyObject,
};
use crate::{
common::{
hash,
lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard},
wtf8::{Wtf8, Wtf8Buf},
},
object::{Traverse, TraverseFn},
};
use alloc::fmt;
use core::mem::size_of;
use core::ops::ControlFlow;
use core::sync::atomic::{
AtomicU64,
Ordering::{Acquire, Release},
};
use num_traits::ToPrimitive;
type HashValue = hash::PyHash;
type HashIndex = hash::PyHash;
type IndexIndex = usize;
type EntryIndex = usize;
pub struct Dict<T = PyObjectRef> {
inner: PyRwLock<DictInner<T>>,
version: AtomicU64,
}
unsafe impl<T: Traverse> Traverse for Dict<T> {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.inner.traverse(tracer_fn);
}
}
impl<T> fmt::Debug for Dict<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Debug").finish()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
struct IndexEntry(i64);
impl IndexEntry {
const FREE: Self = Self(-1);
const DUMMY: Self = Self(-2);
const unsafe fn from_index_unchecked(idx: usize) -> Self {
debug_assert!((idx as isize) >= 0);
Self(idx as i64)
}
const fn index(self) -> Option<usize> {
if self.0 >= 0 {
Some(self.0 as usize)
} else {
None
}
}
}
#[derive(Clone)]
struct DictInner<T> {
used: usize,
filled: usize,
indices: Vec<IndexEntry>,
entries: Vec<Option<DictEntry<T>>>,
}
unsafe impl<T: Traverse> Traverse for DictInner<T> {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.entries
.iter()
.map(|v| {
if let Some(v) = v {
v.key.traverse(tracer_fn);
v.value.traverse(tracer_fn);
}
})
.count();
}
}
impl<T: Clone> Clone for Dict<T> {
fn clone(&self) -> Self {
Self {
inner: PyRwLock::new(self.inner.read().clone()),
version: AtomicU64::new(0),
}
}
}
impl<T> Default for Dict<T> {
fn default() -> Self {
Self {
inner: PyRwLock::new(DictInner {
used: 0,
filled: 0,
indices: vec![IndexEntry::FREE; 8],
entries: Vec::new(),
}),
version: AtomicU64::new(0),
}
}
}
#[derive(Clone)]
struct DictEntry<T> {
hash: HashValue,
key: PyObjectRef,
index: IndexIndex,
value: T,
}
static_assertions::assert_eq_size!(DictEntry<PyObjectRef>, Option<DictEntry<PyObjectRef>>);
#[derive(Debug, PartialEq, Eq)]
pub struct DictSize {
indices_size: usize,
pub entries_size: usize,
pub used: usize,
filled: usize,
}
struct GenIndexes {
idx: HashIndex,
perturb: HashValue,
mask: HashIndex,
}
impl GenIndexes {
const fn new(hash: HashValue, mask: HashIndex) -> Self {
let hash = hash.abs();
Self {
idx: hash,
perturb: hash,
mask,
}
}
const fn next(&mut self) -> usize {
let prev = self.idx;
self.idx = prev
.wrapping_mul(5)
.wrapping_add(self.perturb)
.wrapping_add(1);
self.perturb >>= 5;
(prev & self.mask) as usize
}
}
impl<T> DictInner<T> {
fn resize(&mut self, new_size: usize) {
let new_size = {
let mut i = 1;
while i < new_size {
i <<= 1;
}
i
};
self.indices = vec![IndexEntry::FREE; new_size];
let mask = (new_size - 1) as i64;
for (entry_idx, entry) in self.entries.iter_mut().enumerate() {
if let Some(entry) = entry {
let mut idxs = GenIndexes::new(entry.hash, mask);
loop {
let index_index = idxs.next();
unsafe {
let idx = self.indices.get_unchecked_mut(index_index);
if *idx == IndexEntry::FREE {
*idx = IndexEntry::from_index_unchecked(entry_idx);
entry.index = index_index;
break;
}
}
}
} else {
}
}
self.filled = self.used;
}
fn unchecked_push(
&mut self,
index: IndexIndex,
hash_value: HashValue,
key: PyObjectRef,
value: T,
index_entry: IndexEntry,
) {
let entry = DictEntry {
hash: hash_value,
key,
value,
index,
};
let entry_index = self.entries.len();
self.entries.push(Some(entry));
self.indices[index] = unsafe {
IndexEntry::from_index_unchecked(entry_index)
};
self.used += 1;
if let IndexEntry::FREE = index_entry {
self.filled += 1;
if let Some(new_size) = self.should_resize() {
self.resize(new_size)
}
}
}
const fn size(&self) -> DictSize {
DictSize {
indices_size: self.indices.len(),
entries_size: self.entries.len(),
used: self.used,
filled: self.filled,
}
}
#[inline]
const fn should_resize(&self) -> Option<usize> {
if self.filled * 3 > self.indices.len() * 2 {
Some(self.used * 2)
} else {
None
}
}
#[inline]
fn get_entry_checked(&self, idx: EntryIndex, index_index: IndexIndex) -> Option<&DictEntry<T>> {
match self.entries.get(idx) {
Some(Some(entry)) if entry.index == index_index => Some(entry),
_ => None,
}
}
}
type PopInnerResult<T> = ControlFlow<Option<DictEntry<T>>>;
impl<T: Clone> Dict<T> {
pub fn version(&self) -> u64 {
self.version.load(Acquire)
}
fn bump_version(&self) {
self.version.fetch_add(1, Release);
}
fn read(&self) -> PyRwLockReadGuard<'_, DictInner<T>> {
self.inner.read()
}
fn write(&self) -> PyRwLockWriteGuard<'_, DictInner<T>> {
self.inner.write()
}
pub fn insert<K>(&self, vm: &VirtualMachine, key: &K, value: T) -> PyResult<()>
where
K: DictKey + ?Sized,
{
let hash = key.key_hash(vm)?;
let _removed = loop {
let (entry_index, index_index) = self.lookup(vm, key, hash, None)?;
let mut inner = self.write();
if let Some(index) = entry_index.index() {
if let Some(entry) = inner.entries.get_mut(index) {
let Some(entry) = entry.as_mut() else {
continue;
};
if entry.index == index_index {
let removed = core::mem::replace(&mut entry.value, value);
self.bump_version();
break Some(removed);
} else {
}
} else {
}
} else {
if inner.indices.get(index_index) != Some(&entry_index) {
continue;
}
inner.unchecked_push(index_index, hash, key.to_pyobject(vm), value, entry_index);
self.bump_version();
break None;
}
};
Ok(())
}
pub fn contains<K: DictKey + ?Sized>(&self, vm: &VirtualMachine, key: &K) -> PyResult<bool> {
let key_hash = key.key_hash(vm)?;
let (entry, _) = self.lookup(vm, key, key_hash, None)?;
Ok(entry.index().is_some())
}
#[cfg_attr(feature = "flame-it", flame("Dict"))]
pub fn get<K: DictKey + ?Sized>(&self, vm: &VirtualMachine, key: &K) -> PyResult<Option<T>> {
let hash = key.key_hash(vm)?;
self._get_inner(vm, key, hash)
}
pub fn hint_for_key<K: DictKey + ?Sized>(
&self,
vm: &VirtualMachine,
key: &K,
) -> PyResult<Option<u16>> {
let hash = key.key_hash(vm)?;
let (entry, _) = self.lookup(vm, key, hash, None)?;
let Some(index) = entry.index() else {
return Ok(None);
};
Ok(u16::try_from(index).ok())
}
pub fn get_hint<K: DictKey + ?Sized>(
&self,
vm: &VirtualMachine,
key: &K,
hint: usize,
) -> PyResult<Option<T>> {
let (entry_key, entry_value) = {
let inner = self.read();
let Some(Some(entry)) = inner.entries.get(hint) else {
return Ok(None);
};
if key.key_is(&entry.key) {
return Ok(Some(entry.value.clone()));
}
(entry.key.clone(), entry.value.clone())
};
if key.key_eq(vm, &entry_key)? {
Ok(Some(entry_value))
} else {
Ok(None)
}
}
fn _get_inner<K: DictKey + ?Sized>(
&self,
vm: &VirtualMachine,
key: &K,
hash: HashValue,
) -> PyResult<Option<T>> {
let ret = loop {
let (entry, index_index) = self.lookup(vm, key, hash, None)?;
if let Some(index) = entry.index() {
let inner = self.read();
if let Some(entry) = inner.get_entry_checked(index, index_index) {
break Some(entry.value.clone());
} else {
continue;
}
} else {
break None;
}
};
Ok(ret)
}
pub fn get_chain<K: DictKey + ?Sized>(
&self,
other: &Self,
vm: &VirtualMachine,
key: &K,
) -> PyResult<Option<T>> {
let hash = key.key_hash(vm)?;
if let Some(x) = self._get_inner(vm, key, hash)? {
Ok(Some(x))
} else {
other._get_inner(vm, key, hash)
}
}
pub fn clear(&self) {
let _removed = {
let mut inner = self.write();
inner.indices.clear();
inner.indices.resize(8, IndexEntry::FREE);
inner.used = 0;
inner.filled = 0;
self.bump_version();
core::mem::take(&mut inner.entries)
};
}
pub fn delete<K>(&self, vm: &VirtualMachine, key: &K) -> PyResult<()>
where
K: DictKey + ?Sized,
{
if self.remove_if_exists(vm, key)?.is_some() {
Ok(())
} else {
Err(vm.new_key_error(key.to_pyobject(vm)))
}
}
pub fn delete_if_exists<K>(&self, vm: &VirtualMachine, key: &K) -> PyResult<bool>
where
K: DictKey + ?Sized,
{
self.remove_if_exists(vm, key).map(|opt| opt.is_some())
}
pub fn delete_if<K, F>(&self, vm: &VirtualMachine, key: &K, pred: F) -> PyResult<bool>
where
K: DictKey + ?Sized,
F: Fn(&T) -> PyResult<bool>,
{
self.remove_if(vm, key, pred).map(|opt| opt.is_some())
}
pub fn remove_if_exists<K>(&self, vm: &VirtualMachine, key: &K) -> PyResult<Option<T>>
where
K: DictKey + ?Sized,
{
self.remove_if(vm, key, |_| Ok(true))
}
pub(crate) fn remove_if<K, F>(
&self,
vm: &VirtualMachine,
key: &K,
pred: F,
) -> PyResult<Option<T>>
where
K: DictKey + ?Sized,
F: Fn(&T) -> PyResult<bool>,
{
let hash = key.key_hash(vm)?;
let removed = loop {
let lookup = self.lookup(vm, key, hash, None)?;
match self.pop_inner_if(lookup, &pred)? {
ControlFlow::Break(entry) => break entry,
ControlFlow::Continue(()) => continue,
}
};
Ok(removed.map(|entry| entry.value))
}
pub fn delete_or_insert(&self, vm: &VirtualMachine, key: &PyObject, value: T) -> PyResult<()> {
let hash = key.key_hash(vm)?;
let _removed = loop {
let lookup = self.lookup(vm, key, hash, None)?;
let (entry, index_index) = lookup;
if entry.index().is_some() {
match self.pop_inner(lookup) {
ControlFlow::Break(Some(entry)) => break Some(entry),
_ => continue,
}
} else {
let mut inner = self.write();
if inner.indices.get(index_index) != Some(&entry) {
continue;
}
inner.unchecked_push(index_index, hash, key.to_owned(), value, entry);
self.bump_version();
break None;
}
};
Ok(())
}
pub fn setdefault<K, F>(&self, vm: &VirtualMachine, key: &K, default: F) -> PyResult<T>
where
K: DictKey + ?Sized,
F: FnOnce() -> T,
{
let hash = key.key_hash(vm)?;
let mut default = Some(default);
loop {
let (index_entry, index_index) = self.lookup(vm, key, hash, None)?;
if let Some(index) = index_entry.index() {
let inner = self.read();
if let Some(entry) = inner.get_entry_checked(index, index_index) {
return Ok(entry.value.clone());
}
continue;
}
let mut inner = self.write();
if inner.indices.get(index_index) != Some(&index_entry) {
continue;
}
let value = default
.take()
.expect("default must only be computed on insertion")();
inner.unchecked_push(
index_index,
hash,
key.to_pyobject(vm),
value.clone(),
index_entry,
);
self.bump_version();
return Ok(value);
}
}
#[allow(dead_code)]
pub fn setdefault_entry<K, F>(
&self,
vm: &VirtualMachine,
key: &K,
default: F,
) -> PyResult<(PyObjectRef, T)>
where
K: DictKey + ?Sized,
F: FnOnce() -> T,
{
let hash = key.key_hash(vm)?;
let mut default = Some(default);
loop {
let (index_entry, index_index) = self.lookup(vm, key, hash, None)?;
if let Some(index) = index_entry.index() {
let inner = self.read();
if let Some(entry) = inner.get_entry_checked(index, index_index) {
return Ok((entry.key.clone(), entry.value.clone()));
}
continue;
}
let mut inner = self.write();
if inner.indices.get(index_index) != Some(&index_entry) {
continue;
}
let value = default
.take()
.expect("default must only be computed on insertion")();
let key_obj = key.to_pyobject(vm);
let ret = (key_obj.clone(), value.clone());
inner.unchecked_push(index_index, hash, key_obj, value, index_entry);
self.bump_version();
return Ok(ret);
}
}
pub fn len(&self) -> usize {
self.read().used
}
pub fn size(&self) -> DictSize {
self.read().size()
}
pub fn next_entry(&self, mut position: EntryIndex) -> Option<(usize, PyObjectRef, T)> {
let inner = self.read();
loop {
let entry = inner.entries.get(position)?;
position += 1;
if let Some(entry) = entry {
break Some((position, entry.key.clone(), entry.value.clone()));
}
}
}
pub fn prev_entry(&self, mut position: EntryIndex) -> Option<(usize, PyObjectRef, T)> {
let inner = self.read();
loop {
let entry = inner.entries.get(position)?;
position = position.saturating_sub(1);
if let Some(entry) = entry {
break Some((position, entry.key.clone(), entry.value.clone()));
}
}
}
pub fn len_from_entry_index(&self, position: EntryIndex) -> usize {
self.read().entries.len().saturating_sub(position)
}
pub fn has_changed_size(&self, old: &DictSize) -> bool {
let current = self.read().size();
current != *old
}
pub fn keys(&self) -> Vec<PyObjectRef> {
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| v.key.clone()))
.collect()
}
pub fn values(&self) -> Vec<T> {
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| v.value.clone()))
.collect()
}
pub fn items(&self) -> Vec<(PyObjectRef, T)> {
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| (v.key.clone(), v.value.clone())))
.collect()
}
pub fn try_fold_keys<Acc, Fold>(&self, init: Acc, f: Fold) -> PyResult<Acc>
where
Fold: FnMut(Acc, &PyObject) -> PyResult<Acc>,
{
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| v.key.as_object()))
.try_fold(init, f)
}
#[cfg_attr(feature = "flame-it", flame("Dict"))]
fn lookup<K: DictKey + ?Sized>(
&self,
vm: &VirtualMachine,
key: &K,
hash_value: HashValue,
mut lock: Option<PyRwLockReadGuard<'_, DictInner<T>>>,
) -> PyResult<LookupResult> {
let mut idxs = None;
let mut free_slot = None;
let ret = 'outer: loop {
let (entry_key, ret) = {
let inner = lock.take().unwrap_or_else(|| self.read());
let mask = (inner.indices.len() - 1) as i64;
let idxs = idxs.get_or_insert_with(|| GenIndexes::new(hash_value, mask));
if idxs.mask != mask {
*idxs = GenIndexes::new(hash_value, mask);
free_slot = None;
}
loop {
let index_index = idxs.next();
let index_entry = *unsafe {
inner.indices.get_unchecked(index_index)
};
match index_entry {
IndexEntry::DUMMY => {
if free_slot.is_none() {
free_slot = Some(index_index);
}
}
IndexEntry::FREE => {
let idxs = match free_slot {
Some(free) => (IndexEntry::DUMMY, free),
None => (IndexEntry::FREE, index_index),
};
return Ok(idxs);
}
idx => {
let entry = unsafe {
let i = idx.index().unwrap_unchecked();
inner.entries.get_unchecked(i).as_ref().unwrap_unchecked()
};
let ret = (idx, index_index);
if key.key_is(&entry.key) {
break 'outer ret;
} else if entry.hash == hash_value {
break (entry.key.clone(), ret);
} else {
}
}
}
}
};
if key.key_eq(vm, &entry_key)? {
break 'outer ret;
} else {
}
};
Ok(ret)
}
fn pop_inner(&self, lookup: LookupResult) -> PopInnerResult<T> {
self.pop_inner_if(lookup, |_| Ok::<_, core::convert::Infallible>(true))
.unwrap_or_else(|x| match x {})
}
fn pop_inner_if<E>(
&self,
lookup: LookupResult,
pred: impl Fn(&T) -> Result<bool, E>,
) -> Result<PopInnerResult<T>, E> {
let (entry_index, index_index) = lookup;
let Some(entry_index) = entry_index.index() else {
return Ok(ControlFlow::Break(None));
};
let inner = &mut *self.write();
let slot = if let Some(slot) = inner.entries.get_mut(entry_index) {
slot
} else {
return Ok(ControlFlow::Continue(()));
};
match slot {
Some(entry) if entry.index == index_index => {
if !pred(&entry.value)? {
return Ok(ControlFlow::Break(None));
}
}
_ => return Ok(ControlFlow::Continue(())),
}
*unsafe {
inner.indices.get_unchecked_mut(index_index)
} = IndexEntry::DUMMY;
inner.used -= 1;
let removed = slot.take();
self.bump_version();
Ok(ControlFlow::Break(removed))
}
pub fn pop<K: DictKey + ?Sized>(&self, vm: &VirtualMachine, key: &K) -> PyResult<Option<T>> {
let hash_value = key.key_hash(vm)?;
let removed = loop {
let lookup = self.lookup(vm, key, hash_value, None)?;
match self.pop_inner(lookup) {
ControlFlow::Break(entry) => break entry.map(|e| e.value),
ControlFlow::Continue(()) => continue,
}
};
Ok(removed)
}
pub fn pop_back(&self) -> Option<(PyObjectRef, T)> {
let inner = &mut *self.write();
let entry = loop {
let entry = inner.entries.pop()?;
if let Some(entry) = entry {
break entry;
}
};
inner.used -= 1;
*unsafe {
inner.indices.get_unchecked_mut(entry.index)
} = IndexEntry::DUMMY;
self.bump_version();
Some((entry.key, entry.value))
}
pub fn sizeof(&self) -> usize {
let inner = self.read();
size_of::<Self>()
+ size_of::<DictInner<T>>()
+ inner.indices.len() * size_of::<i64>()
+ inner.entries.len() * size_of::<DictEntry<T>>()
}
pub fn drain_entries(&mut self) -> impl Iterator<Item = (PyObjectRef, T)> + '_ {
let inner = self.inner.get_mut();
inner.used = 0;
inner.filled = 0;
inner.indices.iter_mut().for_each(|i| *i = IndexEntry::FREE);
inner.entries.drain(..).flatten().map(|e| (e.key, e.value))
}
}
type LookupResult = (IndexEntry, IndexIndex);
pub trait DictKey {
type Owned: ToPyObject;
fn _to_owned(&self, vm: &VirtualMachine) -> Self::Owned;
fn to_pyobject(&self, vm: &VirtualMachine) -> PyObjectRef {
self._to_owned(vm).to_pyobject(vm)
}
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue>;
fn key_is(&self, other: &PyObject) -> bool;
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool>;
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize>;
}
impl DictKey for PyObject {
type Owned = PyObjectRef;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline(always)]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
self.hash(vm)
}
#[inline(always)]
fn key_is(&self, other: &PyObject) -> bool {
self.is(other)
}
#[inline(always)]
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
vm.identical_or_equal(self, other_key)
}
#[inline]
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
self.try_index(vm)?.try_to_primitive(vm)
}
}
impl DictKey for Py<PyStr> {
type Owned = PyStrRef;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
Ok(self.hash(vm))
}
#[inline(always)]
fn key_is(&self, other: &PyObject) -> bool {
self.is(other)
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
if self.is(other_key) {
Ok(true)
} else if let Some(pystr) = other_key.downcast_ref_if_exact::<PyStr>(vm) {
Ok(self.as_wtf8() == pystr.as_wtf8())
} else {
vm.bool_eq(self.as_object(), other_key)
}
}
#[inline(always)]
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
self.as_object().key_as_isize(vm)
}
}
impl DictKey for Py<PyUtf8Str> {
type Owned = PyUtf8StrRef;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
self.as_pystr().key_hash(vm)
}
#[inline(always)]
fn key_is(&self, other: &PyObject) -> bool {
self.as_pystr().key_is(other)
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
self.as_pystr().key_eq(vm, other_key)
}
#[inline(always)]
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
self.as_pystr().key_as_isize(vm)
}
}
impl DictKey for PyStrInterned {
type Owned = PyRefExact<PyStr>;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
let zelf: &'static Self = unsafe { &*(self as *const _) };
zelf.to_exact()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
(**self).key_hash(vm)
}
#[inline]
fn key_is(&self, other: &PyObject) -> bool {
(**self).key_is(other)
}
#[inline]
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
(**self).key_eq(vm, other_key)
}
#[inline]
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
(**self).key_as_isize(vm)
}
}
impl DictKey for PyExact<PyStr> {
type Owned = PyRefExact<PyStr>;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline(always)]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
(**self).key_hash(vm)
}
#[inline(always)]
fn key_is(&self, other: &PyObject) -> bool {
(**self).key_is(other)
}
#[inline(always)]
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
(**self).key_eq(vm, other_key)
}
#[inline(always)]
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
(**self).key_as_isize(vm)
}
}
impl DictKey for str {
type Owned = String;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
Ok(vm.state.hash_secret.hash_str(self))
}
#[inline(always)]
fn key_is(&self, _other: &PyObject) -> bool {
false
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
if let Some(pystr) = other_key.downcast_ref_if_exact::<PyStr>(vm) {
Ok(pystr.as_wtf8() == self)
} else {
let s = vm.ctx.new_str(self);
s.key_eq(vm, other_key)
}
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
Err(vm.new_type_error("'str' object cannot be interpreted as an integer"))
}
}
impl DictKey for String {
type Owned = Self;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.clone()
}
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
self.as_str().key_hash(vm)
}
fn key_is(&self, other: &PyObject) -> bool {
self.as_str().key_is(other)
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
self.as_str().key_eq(vm, other_key)
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
self.as_str().key_as_isize(vm)
}
}
impl DictKey for Wtf8 {
type Owned = Wtf8Buf;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
Ok(vm.state.hash_secret.hash_bytes(self.as_bytes()))
}
#[inline(always)]
fn key_is(&self, _other: &PyObject) -> bool {
false
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
if let Some(pystr) = other_key.downcast_ref_if_exact::<PyStr>(vm) {
Ok(pystr.as_wtf8() == self)
} else {
let s = vm.ctx.new_str(self);
s.key_eq(vm, other_key)
}
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
Err(vm.new_type_error("'str' object cannot be interpreted as an integer"))
}
}
impl DictKey for Wtf8Buf {
type Owned = Self;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.clone()
}
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
(**self).key_hash(vm)
}
fn key_is(&self, other: &PyObject) -> bool {
(**self).key_is(other)
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
(**self).key_eq(vm, other_key)
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
(**self).key_as_isize(vm)
}
}
impl DictKey for [u8] {
type Owned = Vec<u8>;
#[inline(always)]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.to_owned()
}
#[inline]
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
Ok(vm.state.hash_secret.hash_bytes(self))
}
#[inline(always)]
fn key_is(&self, _other: &PyObject) -> bool {
false
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
if let Some(pystr) = other_key.downcast_ref_if_exact::<PyBytes>(vm) {
Ok(pystr.as_bytes() == self)
} else {
let s = vm.ctx.new_bytes(self.to_vec());
s.key_eq(vm, other_key)
}
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
Err(vm.new_type_error("'str' object cannot be interpreted as an integer"))
}
}
impl DictKey for Vec<u8> {
type Owned = Self;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
self.clone()
}
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
self.as_slice().key_hash(vm)
}
fn key_is(&self, other: &PyObject) -> bool {
self.as_slice().key_is(other)
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
self.as_slice().key_eq(vm, other_key)
}
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize> {
self.as_slice().key_as_isize(vm)
}
}
impl DictKey for usize {
type Owned = Self;
#[inline]
fn _to_owned(&self, _vm: &VirtualMachine) -> Self::Owned {
*self
}
fn key_hash(&self, _vm: &VirtualMachine) -> PyResult<HashValue> {
Ok(hash::hash_usize(*self))
}
fn key_is(&self, _other: &PyObject) -> bool {
false
}
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool> {
if let Some(int) = other_key.downcast_ref_if_exact::<PyInt>(vm) {
if let Some(i) = int.as_bigint().to_usize() {
Ok(i == *self)
} else {
Ok(false)
}
} else {
let int = vm.ctx.new_int(*self);
vm.bool_eq(int.as_ref(), other_key)
}
}
fn key_as_isize(&self, _vm: &VirtualMachine) -> PyResult<isize> {
Ok(*self as isize)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Interpreter, common::ascii};
#[test]
fn test_insert() {
Interpreter::without_stdlib(Default::default()).enter(|vm| {
let dict = Dict::default();
assert_eq!(0, dict.len());
let key1 = vm.new_pyobj(true);
let value1 = vm.new_pyobj(ascii!("abc"));
dict.insert(vm, &*key1, value1).unwrap();
assert_eq!(1, dict.len());
let key2 = vm.new_pyobj(ascii!("x"));
let value2 = vm.new_pyobj(ascii!("def"));
dict.insert(vm, &*key2, value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.insert(vm, &*key1, value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.delete(vm, &*key1).unwrap();
assert_eq!(1, dict.len());
dict.insert(vm, &*key1, value2.clone()).unwrap();
assert_eq!(2, dict.len());
assert!(dict.contains(vm, &*key1).unwrap());
assert!(dict.contains(vm, "x").unwrap());
let val = dict.get(vm, "x").unwrap().unwrap();
vm.bool_eq(&val, &value2)
.expect("retrieved value must be equal to inserted value.");
})
}
macro_rules! hash_tests {
($($name:ident: $example_hash:expr,)*) => {
$(
#[test]
fn $name() {
check_hash_equivalence($example_hash);
}
)*
}
}
hash_tests! {
test_abc: "abc",
test_x: "x",
}
fn check_hash_equivalence(text: &str) {
Interpreter::without_stdlib(Default::default()).enter(|vm| {
let value1 = text;
let value2 = vm.new_pyobj(value1.to_owned());
let hash1 = value1.key_hash(vm).expect("Hash should not fail.");
let hash2 = value2.key_hash(vm).expect("Hash should not fail.");
assert_eq!(hash1, hash2);
})
}
}