pub(crate) use _functools::module_def;
#[pymodule]
mod _functools {
use crate::{
Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{
PyBoundMethod, PyDict, PyDictRef, PyGenericAlias, PyTuple, PyType, PyTypeRef, object,
},
common::{hash::PyHash, lock::PyRwLock},
function::{
Either, FuncArgs, KwArgs, OptionalOption, PosArgs, PyComparisonValue, PySetterValue,
},
object::{AsObject, PyAtomicRef, TraverseFn},
protocol::PyIter,
pyclass,
recursion::ReprGuard,
types::{Callable, Constructor, GetDescriptor, PyComparisonOp, Representable},
};
use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use parking_lot::lock_api::RawReentrantMutex as GenericRawReentrantMutex;
use rustpython_common::wtf8::Wtf8Buf;
type RawRMutex = GenericRawReentrantMutex<parking_lot::RawMutex, parking_lot::RawThreadId>;
#[derive(FromArgs)]
struct ReduceArgs {
#[pyarg(positional)]
function: PyObjectRef,
#[pyarg(positional)]
iterable: PyIter,
#[pyarg(any, optional)]
initial: OptionalOption<PyObjectRef>,
}
#[pyfunction]
fn reduce(args: ReduceArgs, vm: &VirtualMachine) -> PyResult {
let ReduceArgs {
function,
iterable,
initial,
} = args;
let mut iter = iterable.iter(vm)?;
let start_value = if let Some(val) = initial.into_option() {
val.unwrap_or_else(|| vm.ctx.none())
} else {
iter.next().transpose()?.ok_or_else(|| {
vm.new_type_error("reduce() of empty iterable with no initial value")
})?
};
let mut accumulator = start_value;
for next_obj in iter {
accumulator = function.call((accumulator, next_obj?), vm)?
}
Ok(accumulator)
}
#[derive(FromArgs)]
struct CmpToKeyArgs {
mycmp: PyObjectRef,
}
#[pyfunction]
fn cmp_to_key(args: CmpToKeyArgs) -> PyKeyWrapper {
PyKeyWrapper {
cmp: args.mycmp,
object: PyRwLock::new(None),
}
}
#[derive(FromArgs)]
struct KeyWrapperCallArgs {
obj: PyObjectRef,
}
#[pyclass(
no_attr,
name = "KeyWrapper",
module = "functools",
unhashable = true,
traverse = "manual"
)]
#[derive(Debug, PyPayload)]
struct PyKeyWrapper {
cmp: PyObjectRef,
object: PyRwLock<Option<PyObjectRef>>,
}
unsafe impl crate::object::Traverse for PyKeyWrapper {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.cmp.traverse(tracer_fn);
self.object.traverse(tracer_fn);
}
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
out.push(core::mem::replace(&mut self.cmp, Context::genesis().none()));
if let Some(object) = self.object.get_mut().take() {
out.push(object);
}
}
}
#[pyclass(with(Callable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))]
impl Py<PyKeyWrapper> {
#[pygetset]
fn obj(&self, vm: &VirtualMachine) -> PyObjectRef {
self.object.read().clone().unwrap_or_else(|| vm.ctx.none())
}
#[pygetset(setter)]
fn set_obj(&self, value: PySetterValue) {
let value = match value {
PySetterValue::Assign(v) => Some(v),
PySetterValue::Delete => None,
};
let old = core::mem::replace(&mut *self.object.write(), value);
drop(old);
}
#[pygetset]
fn __text_signature__(&self) -> &'static str {
"(obj)"
}
#[pyslot]
fn slot_richcompare(
zelf: &PyObject,
other: &PyObject,
op: PyComparisonOp,
vm: &VirtualMachine,
) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
let Some(zelf) = zelf.downcast_ref::<PyKeyWrapper>() else {
return Err(vm.new_type_error(format!(
"unexpected payload for {}",
op.method_name(&vm.ctx).as_str()
)));
};
let Some(other) = other.downcast_ref::<PyKeyWrapper>() else {
return Err(vm.new_type_error("other argument must be K instance"));
};
let x = zelf
.object
.read()
.clone()
.ok_or_else(|| vm.new_attribute_error("object"))?;
let y = other
.object
.read()
.clone()
.ok_or_else(|| vm.new_attribute_error("object"))?;
let res = zelf.cmp.call((x, y), vm)?;
res.rich_compare(vm.ctx.new_int(0).into(), op, vm)
.map(Either::A)
}
}
impl Callable for PyKeyWrapper {
type Args = KeyWrapperCallArgs;
fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult {
Ok(Self {
cmp: zelf.cmp.clone(),
object: PyRwLock::new(Some(args.obj)),
}
.into_pyobject(vm))
}
}
#[pyattr]
#[allow(non_snake_case)]
fn Placeholder(vm: &VirtualMachine) -> PyObjectRef {
let placeholder = PyPlaceholderType.into_pyobject(vm);
let typ = placeholder.class();
typ.set_attr(vm.ctx.intern_str("_instance"), placeholder.clone());
placeholder
}
#[pyattr]
#[pyclass(name = "_PlaceholderType", module = "functools")]
#[derive(Debug, PyPayload)]
pub(super) struct PyPlaceholderType;
impl Constructor for PyPlaceholderType {
type Args = FuncArgs;
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if !args.args.is_empty() || !args.kwargs.is_empty() {
return Err(vm.new_type_error("_PlaceholderType takes no arguments"));
}
if let Some(instance) = cls.get_attr(vm.ctx.intern_str("_instance")) {
return Ok(instance);
}
Ok(Self.into_pyobject(vm))
}
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self)
}
}
#[pyclass(with(Constructor, Representable))]
impl Py<PyPlaceholderType> {
#[pymethod]
fn __reduce__(&self) -> &'static str {
"Placeholder"
}
#[pymethod]
fn __init_subclass__(_cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> {
Err(vm.new_type_error("cannot subclass '_PlaceholderType'"))
}
}
impl Representable for PyPlaceholderType {
#[inline]
fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok("Placeholder".to_owned())
}
}
fn is_placeholder(obj: &PyObject) -> bool {
&*obj.class().name() == "_PlaceholderType"
}
fn count_placeholders(args: &[PyObjectRef]) -> usize {
args.iter().filter(|a| is_placeholder(a)).count()
}
#[pyattr]
#[pyclass(name = "partial", module = "functools", traverse = "manual")]
#[derive(Debug, PyPayload)]
pub(super) struct PyPartial {
#[pymember]
func: PyAtomicRef<PyObject>,
#[pymember]
args: PyAtomicRef<PyTuple>,
#[pymember]
keywords: PyAtomicRef<PyDict>,
phcount: AtomicUsize,
}
unsafe impl crate::object::Traverse for PyPartial {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.func.traverse(tracer_fn);
self.args.traverse(tracer_fn);
self.keywords.traverse(tracer_fn);
}
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
out.push(self.func.store(Context::genesis().none()));
}
}
#[pyclass(
with(Constructor, Callable, GetDescriptor, Representable),
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyPartial {
#[pygetset]
fn __dict__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyDictRef {
zelf.as_object()
.instance_dict()
.map_or_else(|| vm.ctx.new_dict(), |d| d.get_or_insert(vm))
}
#[pygetset(setter)]
fn set___dict__(
zelf: &Py<Self>,
value: PySetterValue,
vm: &VirtualMachine,
) -> PyResult<()> {
object::object_generic_set_dict(zelf.as_object().to_owned(), value, vm)
}
#[pymethod]
fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyObjectRef {
let func = zelf.func.load_owned();
let args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let partial_type = zelf.class();
let dict_obj = match zelf.as_object().dict() {
Some(dict) if !dict.is_empty() => dict.into(),
_ => vm.ctx.none(),
};
let state =
vm.ctx
.new_tuple(vec![func.clone(), args.into(), keywords.into(), dict_obj]);
vm.ctx
.new_tuple(vec![
partial_type.to_owned().into(),
vm.ctx.new_tuple(vec![func]).into(),
state.into(),
])
.into()
}
#[pymethod]
fn __setstate__(zelf: &Py<Self>, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let state = object;
let state_tuple = state
.downcast::<PyTuple>()
.map_err(|_| vm.new_type_error("argument to __setstate__ must be a tuple"))?;
if state_tuple.as_slice().len() != 4 {
return Err(vm.new_type_error(format!(
"expected 4 items in state, got {}",
state_tuple.as_slice().len()
)));
}
let func = &state_tuple.as_slice()[0];
let args = &state_tuple.as_slice()[1];
let kwds = &state_tuple.as_slice()[2];
let dict = &state_tuple.as_slice()[3];
if !func.is_callable() {
return Err(vm.new_type_error("invalid partial state"));
}
if !args.fast_isinstance(vm.ctx.types.tuple_type) {
return Err(vm.new_type_error("invalid partial state"));
}
let args_tuple = match args.clone().downcast::<PyTuple>() {
Ok(tuple) if tuple.class().is(vm.ctx.types.tuple_type) => tuple,
_ => {
let elements: Vec<PyObjectRef> = args.try_to_value(vm)?;
vm.ctx.new_tuple(elements)
}
};
let keywords_dict = if kwds.is(&vm.ctx.none) {
vm.ctx.new_dict()
} else {
let dict = kwds
.clone()
.downcast::<PyDict>()
.map_err(|_| vm.new_type_error("invalid partial state"))?;
if dict.class().is(vm.ctx.types.dict_type) {
dict
} else {
let new_dict = vm.ctx.new_dict();
for (key, value) in dict {
new_dict.set_item(&*key, value, vm)?;
}
new_dict
}
};
let args_slice = args_tuple.as_slice();
if !args_slice.is_empty() && is_placeholder(args_slice.last().unwrap()) {
return Err(vm.new_type_error("trailing Placeholders are not allowed"));
}
let phcount = count_placeholders(args_slice);
zelf.func.store(func.clone());
zelf.args.store(args_tuple);
zelf.keywords.store(keywords_dict);
zelf.phcount.store(phcount, Ordering::Relaxed);
let Some(instance_dict) = zelf.as_object().dict() else {
return Ok(());
};
if dict.is(&vm.ctx.none) {
instance_dict.clear();
return Ok(());
}
let dict_obj = dict
.clone()
.downcast::<PyDict>()
.map_err(|_| vm.new_type_error("invalid partial state"))?;
instance_dict.clear();
for (key, value) in dict_obj {
instance_dict.set_item(&*key, value, vm)?;
}
Ok(())
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,
object: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, object, vm)
}
}
#[derive(FromArgs)]
pub(crate) struct PartialSig {
#[pyarg(positional)]
func: PyObjectRef,
#[pyarg(flatten)]
args: PosArgs<PyObjectRef>,
#[pyarg(flatten)]
keywords: KwArgs<PyObjectRef, crate::function::NameKeywords>,
}
impl Constructor for PyPartial {
type Args = PartialSig;
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let zelf = Self::py_new_funcargs(args, vm)?;
zelf.into_ref_with_type(vm, cls).map(Into::into)
}
fn py_new(
_cls: &crate::Py<crate::builtins::PyType>,
_args: Self::Args,
vm: &VirtualMachine,
) -> PyResult<Self> {
let Self::Args {
func,
args,
keywords,
} = _args;
let _ = (func, args, keywords);
Err(vm.new_type_error("use slot_new"))
}
}
impl PyPartial {
fn py_new_funcargs(args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
let (func, args_slice) = args
.args
.split_first()
.ok_or_else(|| vm.new_type_error("partial expected at least 1 argument, got 0"))?;
if !func.is_callable() {
return Err(vm.new_type_error("the first argument must be callable"));
}
for (key, value) in &args.kwargs {
if is_placeholder(value) {
return Err(vm.new_type_error(format!(
"Placeholder cannot be passed as a keyword argument to partial(). \
Did you mean partial(..., {key}=Placeholder, ...)(value)?"
)));
}
}
let (final_func, final_args, final_keywords) =
if let Some(partial) = func.downcast_ref::<Self>() {
let stored_args_obj = partial.args.load_owned();
let stored_func = partial.func.load_owned();
let stored_keywords = partial.keywords.load_owned();
let stored_args = stored_args_obj.as_slice();
let mut merged_args = Vec::with_capacity(stored_args.len() + args_slice.len());
let mut new_args_iter = args_slice.iter();
for stored_arg in stored_args {
if is_placeholder(stored_arg) {
if let Some(new_arg) = new_args_iter.next() {
merged_args.push(new_arg.clone());
} else {
merged_args.push(stored_arg.clone());
}
} else {
merged_args.push(stored_arg.clone());
}
}
merged_args.extend(new_args_iter.cloned());
(stored_func, merged_args, stored_keywords)
} else {
(func.clone(), args_slice.to_vec(), vm.ctx.new_dict())
};
if !final_args.is_empty() && is_placeholder(final_args.last().unwrap()) {
return Err(vm.new_type_error("trailing Placeholders are not allowed"));
}
let phcount = count_placeholders(&final_args);
for (key, value) in args.kwargs {
final_keywords.set_item(vm.ctx.intern_str(key), value, vm)?;
}
Ok(Self {
func: PyAtomicRef::from(final_func),
args: PyAtomicRef::from(vm.ctx.new_tuple(final_args)),
keywords: PyAtomicRef::from(final_keywords),
phcount: AtomicUsize::new(phcount),
})
}
}
impl Callable for PyPartial {
type Args = FuncArgs;
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let func = zelf.func.load_owned();
let stored_args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let phcount = zelf.phcount.load(Ordering::Relaxed);
if phcount > 0 && args.args.len() < phcount {
return Err(vm.new_type_error(format!(
"missing positional arguments in 'partial' call; expected at least {}, got {}",
phcount,
args.args.len()
)));
}
let mut combined_args =
Vec::with_capacity(stored_args.as_slice().len() + args.args.len());
let mut new_args_iter = args.args.iter();
for stored_arg in stored_args.as_slice() {
if is_placeholder(stored_arg) {
if let Some(new_arg) = new_args_iter.next() {
combined_args.push(new_arg.clone());
} else {
combined_args.push(stored_arg.clone());
}
} else {
combined_args.push(stored_arg.clone());
}
}
combined_args.extend(new_args_iter.cloned());
let mut final_kwargs = crate::function::KwArgsMap::default();
for (key, value) in &*keywords {
let key_str = key
.downcast_ref::<crate::builtins::PyStr>()
.ok_or_else(|| vm.new_type_error("keywords must be strings"))?;
final_kwargs.insert(key_str.as_wtf8().to_owned(), value);
}
for (key, value) in args.kwargs {
final_kwargs.insert(key, value);
}
func.call(FuncArgs::new(combined_args, KwArgs::new(final_kwargs)), vm)
}
}
impl GetDescriptor for PyPartial {
fn descr_get(
zelf: &PyObject,
obj: Option<&PyObject>,
_cls: Option<&PyObject>,
vm: &VirtualMachine,
) -> PyResult {
let obj = match obj {
Some(obj) if !vm.is_none(obj) => obj,
_ => return Ok(zelf.to_owned()),
};
Ok(PyBoundMethod::new(obj.to_owned(), zelf.to_owned())
.into_ref(&vm.ctx)
.into())
}
}
impl Representable for PyPartial {
#[inline]
fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
let obj = zelf.as_object();
if let Some(_guard) = ReprGuard::enter(vm, obj) {
let func = zelf.func.load_owned();
let args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let qualname = zelf.class().__qualname__(vm);
let qualname_wtf8 = qualname
.downcast_ref::<crate::builtins::PyStr>()
.map_or_else(
|| Wtf8Buf::from(zelf.class().name().to_owned()),
|s| s.as_wtf8().to_owned(),
);
let module = zelf.class().__module__(vm)?;
let mut result = Wtf8Buf::new();
if let Ok(module_str) = module.downcast::<crate::builtins::PyStr>() {
let module_name = module_str.as_wtf8();
if module_name != "builtins" && !module_name.is_empty() {
result.push_wtf8(module_name);
result.push_char('.');
}
}
result.push_wtf8(&qualname_wtf8);
result.push_char('(');
result.push_wtf8(func.repr(vm)?.as_wtf8());
for arg in args.as_slice() {
result.push_str(", ");
result.push_wtf8(arg.repr(vm)?.as_wtf8());
}
for (key, value) in &*keywords {
result.push_str(", ");
let key_str = if let Ok(s) = key.clone().downcast::<crate::builtins::PyStr>() {
s
} else {
key.str(vm)?
};
result.push_wtf8(key_str.as_wtf8());
result.push_char('=');
result.push_wtf8(value.repr(vm)?.as_wtf8());
}
result.push_char(')');
Ok(result)
} else {
Ok(Wtf8Buf::from("..."))
}
}
}
struct RMutexGuard<'a>(&'a RawRMutex);
impl<'a> RMutexGuard<'a> {
fn acquire(mu: &'a RawRMutex) -> Self {
mu.lock();
Self(mu)
}
}
impl Drop for RMutexGuard<'_> {
fn drop(&mut self) {
unsafe { self.0.unlock() };
}
}
#[pyclass(no_attr, name = "_lru_list_elem", module = "functools", traverse)]
#[derive(Debug, PyPayload)]
struct PyLruListElem {
#[pytraverse(skip)]
hash: PyHash,
result: PyObjectRef,
}
#[pyclass(flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))]
impl PyLruListElem {}
#[pyattr]
#[pyclass(name = "_lru_cache_wrapper", module = "functools", traverse = "manual")]
#[derive(PyPayload)]
pub(super) struct PyLruCacheWrapper {
func: PyObjectRef,
maxsize: Option<usize>,
typed: bool,
keyword_marker: PyObjectRef,
cache_info_type: PyObjectRef,
hits: AtomicU64,
misses: AtomicU64,
cache: PyRwLock<PyDictRef>,
lock: RawRMutex,
}
unsafe impl crate::object::Traverse for PyLruCacheWrapper {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.func.traverse(tracer_fn);
self.keyword_marker.traverse(tracer_fn);
self.cache_info_type.traverse(tracer_fn);
self.cache.traverse(tracer_fn);
}
}
impl core::fmt::Debug for PyLruCacheWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.pad("lru_cache_wrapper")
}
}
#[derive(FromArgs)]
pub(super) struct LruCacheWrapperArgs {
#[pyarg(positional)]
function: PyObjectRef,
#[pyarg(positional)]
maxsize: Option<isize>,
#[pyarg(positional)]
typed: bool,
#[pyarg(positional)]
cache_info_type: PyObjectRef,
}
impl Constructor for PyLruCacheWrapper {
type Args = LruCacheWrapperArgs;
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
if !args.function.is_callable() {
return Err(vm.new_type_error("the first argument must be callable"));
}
let maxsize = args.maxsize.map(|n| n.max(0) as usize);
Ok(Self {
func: args.function,
maxsize,
typed: args.typed,
keyword_marker: vm
.ctx
.new_base_object(vm.ctx.types.object_type.to_owned(), None),
cache_info_type: args.cache_info_type,
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
cache: PyRwLock::new(vm.ctx.new_dict()),
lock: RawRMutex::INIT,
})
}
}
impl PyLruCacheWrapper {
fn make_key(&self, args: &FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
let mut elements: Vec<PyObjectRef> = args.args.clone();
if !args.kwargs.is_empty() {
elements.push(self.keyword_marker.clone());
for (name, value) in &args.kwargs {
elements.push(vm.ctx.new_str(name.clone()).into());
elements.push(value.to_owned());
}
}
if self.typed {
elements.extend(args.args.iter().map(|a| a.class().to_owned().into()));
if !args.kwargs.is_empty() {
elements.extend(
(&args.kwargs)
.into_iter()
.map(|(_, v)| v.class().to_owned().into()),
);
}
}
vm.ctx.new_tuple(elements).into()
}
fn touch_key(
key: &PyObject,
hash: PyHash,
value: &PyObject,
cache: &Py<PyDict>,
vm: &VirtualMachine,
) -> PyResult<()> {
cache.del_item_known_hash(key, hash, vm)?;
cache.set_item_known_hash(key, hash, value.to_owned(), vm)?;
Ok(())
}
fn evict_if_full(maxsize: usize, cache: &Py<PyDict>, vm: &VirtualMachine) -> PyResult<()> {
if cache.__len__() <= maxsize {
return Ok(());
}
let oldest = cache.into_iter().next();
if let Some((oldest_key, oldest_val)) = oldest {
let Some(elem) = oldest_val.downcast_ref::<PyLruListElem>() else {
return Err(vm.new_type_error("lru cache entry is corrupted"));
};
cache.del_item_known_hash(oldest_key.as_object(), elem.hash, vm)?;
}
Ok(())
}
}
#[pyclass(
with(Constructor, Callable, GetDescriptor),
flags(HAS_DICT, HAS_WEAKREF)
)]
impl Py<PyLruCacheWrapper> {
#[pymethod]
fn cache_info(&self, vm: &VirtualMachine) -> PyResult {
let hits = self.hits.load(Ordering::Relaxed);
let misses = self.misses.load(Ordering::Relaxed);
let currsize = self.cache.read().__len__();
let maxsize: PyObjectRef = match self.maxsize {
Some(n) => vm.ctx.new_int(n).into(),
None => vm.ctx.none(),
};
self.cache_info_type
.call((hits, misses, maxsize, currsize), vm)
}
#[pymethod]
fn cache_clear(&self, vm: &VirtualMachine) {
let new_cache = vm.ctx.new_dict();
let old_cache = {
let _guard = RMutexGuard::acquire(&self.lock);
let old_cache = core::mem::replace(&mut *self.cache.write(), new_cache);
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
old_cache
};
drop(old_cache);
}
#[pymethod]
fn __reduce__(zelf: &Self, vm: &VirtualMachine) -> PyResult {
zelf.as_object().get_attr("__qualname__", vm)
}
#[pymethod]
fn __copy__(zelf: PyObjectRef) -> PyObjectRef {
zelf
}
#[pymethod]
fn __deepcopy__(zelf: PyObjectRef, _memo: PyObjectRef) -> PyObjectRef {
zelf
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
impl Callable for PyLruCacheWrapper {
type Args = FuncArgs;
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if zelf.maxsize == Some(0) {
zelf.misses.fetch_add(1, Ordering::Relaxed);
return zelf.func.call(args, vm);
}
let key = zelf.make_key(&args, vm);
let hash = key.hash(vm)?;
{
let _guard = RMutexGuard::acquire(&zelf.lock);
let cache = zelf.cache.read().clone();
if let Some(value) = cache.get_item_known_hash(key.as_object(), hash, vm)? {
let result = if zelf.maxsize.is_some() {
let Some(elem) = value.downcast_ref::<PyLruListElem>() else {
return Err(vm.new_type_error("lru cache entry is corrupted"));
};
elem.result.clone()
} else {
value.clone()
};
zelf.hits.fetch_add(1, Ordering::Relaxed);
if zelf.maxsize.is_some() {
Self::touch_key(&key, hash, &value, &cache, vm)?;
}
return Ok(result);
}
zelf.misses.fetch_add(1, Ordering::Relaxed);
}
let result = zelf.func.call(args, vm)?;
{
let _guard = RMutexGuard::acquire(&zelf.lock);
let cache = zelf.cache.read().clone();
if !cache.contains_known_hash(key.as_object(), hash, vm)? {
let entry = if zelf.maxsize.is_some() {
PyLruListElem {
hash,
result: result.clone(),
}
.into_pyobject(vm)
} else {
result.clone()
};
cache.set_item_known_hash(key.as_object(), hash, entry, vm)?;
if let Some(maxsize) = zelf.maxsize {
Self::evict_if_full(maxsize, &cache, vm)?;
}
}
}
Ok(result)
}
}
impl GetDescriptor for PyLruCacheWrapper {
fn descr_get(
zelf: &PyObject,
obj: Option<&PyObject>,
_cls: Option<&PyObject>,
vm: &VirtualMachine,
) -> PyResult {
let obj = match obj {
Some(obj) if !vm.is_none(obj) => obj,
_ => return Ok(zelf.to_owned()),
};
Ok(PyBoundMethod::new(obj.to_owned(), zelf.to_owned())
.into_ref(&vm.ctx)
.into())
}
}
}