Merge pull request #3635 from youknowone/into-object

distinguish IntoObject::into_object() and PyValue::into_pyobject(vm)
This commit is contained in:
Jeong YunWon
2022-04-19 01:51:19 +09:00
committed by GitHub
47 changed files with 1037 additions and 1015 deletions

View File

@@ -3,9 +3,7 @@ use criterion::{
Criterion, Throughput,
};
use rustpython_compiler::Mode;
use rustpython_vm::{
common::ascii, InitParameter, Interpreter, PyObjectWrap, PyResult, PySettings,
};
use rustpython_vm::{common::ascii, InitParameter, Interpreter, PyResult, PySettings};
use std::{
ffi, fs, io,
path::{Path, PathBuf},

View File

@@ -480,7 +480,7 @@ mod array {
$f_swap(self)
}
fn to_object(self, vm: &VirtualMachine) -> PyObjectRef {
$f_to(self).into_object(vm)
$f_to(self).into_pyobject(vm)
}
}
)*};
@@ -1117,7 +1117,7 @@ mod array {
let typecode = vm.ctx.new_str(array.typecode_str());
let bytes = vm.ctx.new_bytes(array.get_bytes().to_vec());
let code = MachineFormatCode::from_typecode(array.typecode()).unwrap();
let code = PyInt::from(u8::from(code)).into_object(vm);
let code = PyInt::from(u8::from(code)).into_pyobject(vm);
let module = vm.import("array", None, 0)?;
let func = module.get_attr("_array_reconstructor", vm)?;
Ok((
@@ -1265,7 +1265,7 @@ mod array {
position: AtomicUsize::new(0),
array: zelf,
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -77,7 +77,7 @@ mod hashlib {
#[pyslot]
fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
Ok(PyHasher::new("md5", HashWrapper::md5()).into_object(vm))
Ok(PyHasher::new("md5", HashWrapper::md5()).into_pyobject(vm))
}
#[pyproperty]

View File

@@ -33,8 +33,8 @@ use crate::{
IterNextIterable, Iterable, PyComparisonOp, Unconstructible, Unhashable,
},
utils::Either,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult,
PyValue, TryFromBorrowedObject, TryFromObject, VirtualMachine,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyRef, PyResult, PyValue,
TryFromBorrowedObject, TryFromObject, VirtualMachine,
};
use bstr::ByteSlice;
use std::{borrow::Cow, mem::size_of};
@@ -751,7 +751,7 @@ static BUFFER_METHODS: BufferMethods = BufferMethods {
impl AsBuffer for PyByteArray {
fn as_buffer(zelf: &PyObjectView<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
Ok(PyBuffer::new(
zelf.to_owned().into_object(),
zelf.to_owned().into(),
BufferDescriptor::simple(zelf.len(), false),
&BUFFER_METHODS,
))
@@ -789,12 +789,12 @@ impl PyByteArray {
Self::sequence_downcast(seq)
.inner()
.concat(other, vm)
.map(|x| PyByteArray::from(x).into_object(vm))
.map(|x| PyByteArray::from(x).into_pyobject(vm))
}),
repeat: Some(|seq, n, vm| {
Self::sequence_downcast(seq)
.mul(n as isize, vm)
.map(|x| x.into_object(vm))
.map(|x| x.into_pyobject(vm))
}),
item: Some(|seq, i, vm| {
Self::sequence_downcast(seq)
@@ -833,7 +833,7 @@ impl Iterable for PyByteArray {
Ok(PyByteArrayIterator {
internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -20,8 +20,8 @@ use crate::{
IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
},
utils::Either,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult,
PyValue, TryFromBorrowedObject, TryFromObject, VirtualMachine,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyRef, PyResult, PyValue,
TryFromBorrowedObject, TryFromObject, VirtualMachine,
};
use bstr::ByteSlice;
use std::{borrow::Cow, mem::size_of, ops::Deref};
@@ -567,7 +567,7 @@ static BUFFER_METHODS: BufferMethods = BufferMethods {
impl AsBuffer for PyBytes {
fn as_buffer(zelf: &PyObjectView<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
let buf = PyBuffer::new(
zelf.to_owned().into_object(),
zelf.to_owned().into(),
BufferDescriptor::simple(zelf.len(), true),
&BUFFER_METHODS,
);
@@ -657,7 +657,7 @@ impl Iterable for PyBytes {
Ok(PyBytesIterator {
internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -374,7 +374,7 @@ impl PyDict {
if let Ok(other) = dicted {
let other_cp = other.copy();
PyDict::merge_dict(&other_cp.entries, zelf, vm)?;
return Ok(other_cp.into_object(vm));
return Ok(other_cp.into_pyobject(vm));
}
Ok(vm.ctx.not_implemented())
}
@@ -385,7 +385,7 @@ impl PyDict {
if let Ok(other) = dicted {
let self_cp = self.copy();
PyDict::merge_dict(&self_cp.entries, other, vm)?;
return Ok(self_cp.into_object(vm));
return Ok(self_cp.into_pyobject(vm));
}
Ok(vm.ctx.not_implemented())
}
@@ -502,7 +502,7 @@ impl Unhashable for PyDict {}
impl Iterable for PyDict {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyDictKeyIterator::new(zelf).into_object(vm))
Ok(PyDictKeyIterator::new(zelf).into_pyobject(vm))
}
}
@@ -737,7 +737,7 @@ macro_rules! dict_view {
impl Iterable for $name {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok($iter_name::new(zelf.dict.clone()).into_object(vm))
Ok($iter_name::new(zelf.dict.clone()).into_pyobject(vm))
}
}
@@ -1011,7 +1011,7 @@ trait ViewSetOps: DictView {
}
ref _set @ PySet => {
let inner = Self::to_set(zelf.to_owned(), vm)?;
let zelf_set = PySet { inner }.into_object(vm);
let zelf_set = PySet { inner }.into_pyobject(vm);
PySet::cmp(zelf_set.downcast_ref().unwrap(), other, op, vm)
}
_ => {

View File

@@ -7,7 +7,7 @@ use crate::{
frame::{Frame, FrameRef},
pyclass::PyClassImpl,
types::{Constructor, Unconstructible},
AsObject, PyContext, PyObjectRef, PyObjectWrap, PyRef, PyResult, VirtualMachine,
AsObject, PyContext, PyObjectRef, PyRef, PyResult, VirtualMachine,
};
pub fn init(context: &PyContext) {
@@ -46,7 +46,7 @@ impl FrameRef {
#[pyproperty]
fn f_locals(self, vm: &VirtualMachine) -> PyResult {
self.locals(vm).map(|x| x.into_object())
self.locals(vm).map(Into::into)
}
#[pyproperty]

View File

@@ -311,9 +311,9 @@ impl PyFunction {
let is_gen = code.flags.contains(bytecode::CodeFlags::IS_GENERATOR);
let is_coro = code.flags.contains(bytecode::CodeFlags::IS_COROUTINE);
match (is_gen, is_coro) {
(true, false) => Ok(PyGenerator::new(frame, self.name()).into_object(vm)),
(false, true) => Ok(PyCoroutine::new(frame, self.name()).into_object(vm)),
(true, true) => Ok(PyAsyncGen::new(frame, self.name()).into_object(vm)),
(true, false) => Ok(PyGenerator::new(frame, self.name()).into_pyobject(vm)),
(false, true) => Ok(PyCoroutine::new(frame, self.name()).into_pyobject(vm)),
(true, true) => Ok(PyAsyncGen::new(frame, self.name()).into_pyobject(vm)),
(false, false) => vm.run_frame_full(frame),
}
}

View File

@@ -142,7 +142,10 @@ impl PyGenericAlias {
vm,
)?;
Ok(PyGenericAlias::new(self.origin.clone(), new_args.to_pyobject(vm), vm).into_object(vm))
Ok(
PyGenericAlias::new(self.origin.clone(), new_args.to_pyobject(vm), vm)
.into_pyobject(vm),
)
}
#[pymethod(magic)]

View File

@@ -58,7 +58,7 @@ impl PyValue for PyInt {
&vm.ctx.types.int_type
}
fn into_object(self, vm: &VirtualMachine) -> PyObjectRef {
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_int(self.value).into()
}

View File

@@ -191,10 +191,10 @@ impl PySequenceIterator {
if let IterStatus::Active(obj) = &internal.status {
let seq = PySequence::with_methods(obj, self.seq_methods.clone());
seq.length(vm)
.map(|x| PyInt::from(x).into_object(vm))
.map(|x| PyInt::from(x).into_pyobject(vm))
.unwrap_or_else(|_| vm.ctx.not_implemented())
} else {
PyInt::from(0).into_object(vm)
PyInt::from(0).into_pyobject(vm)
}
}

View File

@@ -16,8 +16,7 @@ use crate::{
},
utils::collection_repr,
vm::{ReprGuard, VirtualMachine},
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult,
PyValue,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyRef, PyResult, PyValue,
};
use std::{borrow::Cow, fmt, ops::DerefMut};
@@ -412,12 +411,12 @@ impl PyList {
concat: Some(|seq, other, vm| {
Self::sequence_downcast(seq)
.concat(other, vm)
.map(|x| x.into_object())
.map(|x| x.into())
}),
repeat: Some(|seq, n, vm| {
Self::sequence_downcast(seq)
.mul(n as isize, vm)
.map(|x| x.into_object())
.map(|x| x.into())
}),
item: Some(|seq, i, vm| {
Self::sequence_downcast(seq)
@@ -443,7 +442,7 @@ impl PyList {
inplace_repeat: Some(|seq, n, vm| {
let zelf = Self::sequence_downcast(seq);
zelf.borrow_vec_mut().imul(vm, n as isize)?;
Ok(zelf.to_owned().into_object())
Ok(zelf.to_owned().into())
}),
};
}
@@ -453,7 +452,7 @@ impl Iterable for PyList {
Ok(PyListIterator {
internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -19,8 +19,8 @@ use crate::{
sliceable::wrap_index,
types::{AsBuffer, AsMapping, AsSequence, Comparable, Constructor, Hashable, PyComparisonOp},
utils::Either,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult,
PyValue, TryFromBorrowedObject, TryFromObject, VirtualMachine,
AsObject, PyContext, PyObject, PyObjectRef, PyObjectView, PyRef, PyResult, PyValue,
TryFromBorrowedObject, TryFromObject, VirtualMachine,
};
use crossbeam_utils::atomic::AtomicCell;
use itertools::Itertools;
@@ -255,7 +255,7 @@ impl PyMemoryView {
other.init_slice(slice, 0, vm)?;
other.init_len();
Ok(other.into_ref(vm).into_object())
Ok(other.into_ref(vm).into())
}
fn getitem_by_multi_idx(&self, indexes: &[isize], vm: &VirtualMachine) -> PyResult {
@@ -270,7 +270,7 @@ impl PyMemoryView {
if zelf.desc.ndim() == 0 {
// 0-d memoryview can be referenced using mv[...] or mv[()] only
if needle.is(&vm.ctx.ellipsis) {
return Ok(zelf.into_object());
return Ok(zelf.into());
}
if let Some(tuple) = needle.payload::<PyTuple>() {
if tuple.is_empty() {
@@ -539,9 +539,7 @@ impl PyMemoryView {
let mut v = Vec::with_capacity(shape);
for _ in 0..shape {
let obj = self
._to_list(bytes, index + suboffset, dim + 1, vm)?
.into_object();
let obj = self._to_list(bytes, index + suboffset, dim + 1, vm)?.into();
v.push(obj);
index += stride;
}
@@ -938,7 +936,7 @@ impl AsBuffer for PyMemoryView {
Err(vm.new_value_error("operation forbidden on released memoryview object".to_owned()))
} else {
Ok(PyBuffer::new(
zelf.to_owned().into_object(),
zelf.to_owned().into(),
zelf.desc.clone(),
&BUFFER_METHODS,
))

View File

@@ -1264,7 +1264,7 @@ impl Iterable for PyStr {
Ok(PyStrIterator {
internal: PyMutex::new((PositionIterInternal::new(zelf, 0), 0)),
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -171,7 +171,7 @@ impl GetDescriptor for PySuper {
}
let zelf_class = zelf.as_object().class();
if zelf_class.is(&vm.ctx.types.super_type) {
Ok(PySuper::new(zelf.typ.clone(), obj, vm)?.into_object(vm))
Ok(PySuper::new(zelf.typ.clone(), obj, vm)?.into_pyobject(vm))
} else {
let obj = vm.unwrap_or_none(zelf.obj.clone().map(|(o, _)| o));
vm.invoke(&zelf.class(), (zelf.typ.clone(), obj))

View File

@@ -477,19 +477,19 @@ impl PyType {
let mut attributes = dict.to_attributes();
if let Some(f) = attributes.get_mut("__new__") {
if f.class().is(&vm.ctx.types.function_type) {
*f = PyStaticMethod::from(f.clone()).into_object(vm);
*f = PyStaticMethod::from(f.clone()).into_pyobject(vm);
}
}
if let Some(f) = attributes.get_mut("__init_subclass__") {
if f.class().is(&vm.ctx.types.function_type) {
*f = PyClassMethod::from(f.clone()).into_object(vm);
*f = PyClassMethod::from(f.clone()).into_pyobject(vm);
}
}
if let Some(f) = attributes.get_mut("__class_getitem__") {
if f.class().is(&vm.ctx.types.function_type) {
*f = PyClassMethod::from(f.clone()).into_object(vm);
*f = PyClassMethod::from(f.clone()).into_pyobject(vm);
}
}

View File

@@ -242,7 +242,7 @@ impl PyRange {
// always fit in a usize.
length: length.to_usize().unwrap_or(0),
}
.into_object(vm)
.into_pyobject(vm)
} else {
PyLongRangeIterator {
index: AtomicCell::new(0),
@@ -250,7 +250,7 @@ impl PyRange {
step,
length,
}
.into_object(vm)
.into_pyobject(vm)
},
)
}
@@ -284,7 +284,7 @@ impl PyRange {
}
} else {
iter_search(
self.clone().into_object(vm),
self.clone().into_pyobject(vm),
needle,
SearchType::Contains,
vm,
@@ -315,8 +315,13 @@ impl PyRange {
// Fallback to iteration.
Ok(BigInt::from_bytes_be(
Sign::Plus,
&iter_search(self.clone().into_object(vm), needle, SearchType::Index, vm)?
.to_be_bytes(),
&iter_search(
self.clone().into_pyobject(vm),
needle,
SearchType::Index,
vm,
)?
.to_be_bytes(),
))
}
}
@@ -332,7 +337,7 @@ impl PyRange {
} else {
// Dealing with classes who might compare equal with ints in their
// __eq__, slow search.
iter_search(self.clone().into_object(vm), item, SearchType::Count, vm)
iter_search(self.clone().into_pyobject(vm), item, SearchType::Count, vm)
}
}
@@ -493,7 +498,7 @@ impl Iterable for PyRange {
// always fit in a usize.
length: length.to_usize().unwrap_or(0),
}
.into_object(vm))
.into_pyobject(vm))
} else {
Ok(PyLongRangeIterator {
index: AtomicCell::new(0),
@@ -501,7 +506,7 @@ impl Iterable for PyRange {
step: step.clone(),
length,
}
.into_object(vm))
.into_pyobject(vm))
}
}
}

View File

@@ -338,7 +338,7 @@ impl PySetInner {
&PyFrozenSet {
inner: set.inner.copy(),
}
.into_object(vm),
.into_pyobject(vm),
vm,
)
// If operation raised KeyError, report original set (set.remove)
@@ -688,7 +688,7 @@ impl Unhashable for PySet {}
impl Iterable for PySet {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(zelf.inner.iter().into_object(vm))
Ok(zelf.inner.iter().into_pyobject(vm))
}
}
@@ -933,7 +933,7 @@ impl Comparable for PyFrozenSet {
impl Iterable for PyFrozenSet {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(zelf.inner.iter().into_object(vm))
Ok(zelf.inner.iter().into_pyobject(vm))
}
}

View File

@@ -394,7 +394,7 @@ impl Iterable for PyTuple {
Ok(PyTupleIterator {
internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
}
.into_object(vm))
.into_pyobject(vm))
}
}

View File

@@ -7,8 +7,7 @@ use crate::{
function::OptionalArg,
pyclass::PyClassImpl,
types::{Callable, Comparable, Constructor, Hashable, PyComparisonOp},
AsObject, PyContext, PyObject, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue,
VirtualMachine,
AsObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, VirtualMachine,
};
pub use crate::pyobject::PyWeak;
@@ -44,7 +43,8 @@ impl Constructor for PyWeak {
vm: &VirtualMachine,
) -> PyResult {
let weak = referent.downgrade_with_typ(callback.into_option(), cls, vm)?;
Ok(weak.into_object())
let pyref_weak: PyRef<PyWeak> = weak.into();
Ok(pyref_weak.into())
}
}

View File

@@ -0,0 +1,12 @@
use crate::PyObjectRef;
pub trait IntoObject
where
Self: Into<PyObjectRef>,
{
fn into_object(self) -> PyObjectRef {
self.into()
}
}
impl<T> IntoObject for T where T: Into<PyObjectRef> {}

View File

@@ -1,7 +1,9 @@
mod into_object;
mod to_pyobject;
mod transmute_from;
mod try_from;
pub use into_object::IntoObject;
pub use to_pyobject::{ToPyException, ToPyObject, ToPyResult};
pub use transmute_from::TransmuteFromObject;
pub use try_from::{TryFromBorrowedObject, TryFromObject};

View File

@@ -8,7 +8,7 @@ use crate::{
PySlice, PyStr, PyStrRef, PyTraceback, PyTypeRef,
},
bytecode,
convert::ToPyResult,
convert::{IntoObject, ToPyResult},
coroutine::Coro,
exceptions::ExceptionCtor,
function::{ArgMapping, FuncArgs},
@@ -16,8 +16,8 @@ use crate::{
scope::Scope,
stdlib::builtins,
types::PyComparisonOp,
AsObject, PyMethod, PyObject, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue,
TryFromObject, VirtualMachine,
AsObject, PyMethod, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
VirtualMachine,
};
use indexmap::IndexMap;
use itertools::Itertools;
@@ -727,7 +727,7 @@ impl ExecutingFrame<'_> {
bytecode::Instruction::YieldValue => {
let value = self.pop_value();
let value = if self.code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) {
PyAsyncGenWrappedValue(value).into_object(vm)
PyAsyncGenWrappedValue(value).into_pyobject(vm)
} else {
value
};
@@ -1606,7 +1606,7 @@ impl ExecutingFrame<'_> {
defaults,
kw_only_defaults,
)
.into_object(vm);
.into_pyobject(vm);
func_obj.set_attr("__doc__", vm.ctx.none(), vm)?;

View File

@@ -1,738 +0,0 @@
mod argument;
mod arithmetic;
mod buffer;
mod number;
use crate::{
builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef},
convert::{ToPyObject, ToPyResult},
pyobject::PyThreadingConstraint,
AsObject, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use indexmap::IndexMap;
use itertools::Itertools;
use std::{marker::PhantomData, ops::RangeInclusive};
pub use argument::{ArgCallable, ArgIterable, ArgMapping, ArgSequence};
pub use arithmetic::{PyArithmeticValue, PyComparisonValue};
pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike};
pub use number::{ArgIntoBool, ArgIntoComplex, ArgIntoFloat};
pub trait IntoFuncArgs: Sized {
fn into_args(self, vm: &VirtualMachine) -> FuncArgs;
fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
let mut args = self.into_args(vm);
args.prepend_arg(obj);
args
}
}
impl<T> IntoFuncArgs for T
where
T: Into<FuncArgs>,
{
fn into_args(self, _vm: &VirtualMachine) -> FuncArgs {
self.into()
}
}
// A tuple of values that each implement `ToPyObject` represents a sequence of
// arguments that can be bound and passed to a built-in function.
macro_rules! into_func_args_from_tuple {
($(($n:tt, $T:ident)),*) => {
impl<$($T,)*> IntoFuncArgs for ($($T,)*)
where
$($T: ToPyObject,)*
{
#[inline]
fn into_args(self, vm: &VirtualMachine) -> FuncArgs {
let ($($n,)*) = self;
PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into()
}
#[inline]
fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
let ($($n,)*) = self;
PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into()
}
}
};
}
into_func_args_from_tuple!((v1, T1));
into_func_args_from_tuple!((v1, T1), (v2, T2));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
/// The `FuncArgs` struct is one of the most used structs then creating
/// a rust function that can be called from python. It holds both positional
/// arguments, as well as keyword arguments passed to the function.
#[derive(Debug, Default, Clone)]
pub struct FuncArgs {
pub args: Vec<PyObjectRef>,
// sorted map, according to https://www.python.org/dev/peps/pep-0468/
pub kwargs: IndexMap<String, PyObjectRef>,
}
/// Conversion from vector of python objects to function arguments.
impl<A> From<A> for FuncArgs
where
A: Into<PosArgs>,
{
fn from(args: A) -> Self {
FuncArgs {
args: args.into().into_vec(),
kwargs: IndexMap::new(),
}
}
}
impl From<KwArgs> for FuncArgs {
fn from(kwargs: KwArgs) -> Self {
FuncArgs {
args: Vec::new(),
kwargs: kwargs.0,
}
}
}
impl FromArgs for FuncArgs {
fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(std::mem::take(args))
}
}
impl FuncArgs {
pub fn new<A, K>(args: A, kwargs: K) -> Self
where
A: Into<PosArgs>,
K: Into<KwArgs>,
{
let PosArgs(args) = args.into();
let KwArgs(kwargs) = kwargs.into();
Self { args, kwargs }
}
pub fn with_kwargs_names<A, KW>(mut args: A, kwarg_names: KW) -> Self
where
A: ExactSizeIterator<Item = PyObjectRef>,
KW: ExactSizeIterator<Item = String>,
{
// last `kwarg_names.len()` elements of args in order of appearance in the call signature
let total_argc = args.len();
let kwargc = kwarg_names.len();
let posargc = total_argc - kwargc;
let posargs = args.by_ref().take(posargc).collect();
let kwargs = kwarg_names.zip_eq(args).collect::<IndexMap<_, _>>();
FuncArgs {
args: posargs,
kwargs,
}
}
pub fn prepend_arg(&mut self, item: PyObjectRef) {
self.args.reserve_exact(1);
self.args.insert(0, item)
}
pub fn shift(&mut self) -> PyObjectRef {
self.args.remove(0)
}
pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef {
self.kwargs
.get(key)
.cloned()
.unwrap_or_else(|| default.clone())
}
pub fn get_optional_kwarg(&self, key: &str) -> Option<PyObjectRef> {
self.kwargs.get(key).cloned()
}
pub fn get_optional_kwarg_with_type(
&self,
key: &str,
ty: PyTypeRef,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
match self.get_optional_kwarg(key) {
Some(kwarg) => {
if kwarg.fast_isinstance(&ty) {
Ok(Some(kwarg))
} else {
let expected_ty_name = &ty.name();
let kwarg_class = kwarg.class();
let actual_ty_name = &kwarg_class.name();
Err(vm.new_type_error(format!(
"argument of type {} is required for named parameter `{}` (got: {})",
expected_ty_name, key, actual_ty_name
)))
}
}
None => Ok(None),
}
}
pub fn take_positional(&mut self) -> Option<PyObjectRef> {
if self.args.is_empty() {
None
} else {
Some(self.args.remove(0))
}
}
pub fn take_positional_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
self.take_positional().or_else(|| self.take_keyword(name))
}
pub fn take_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
self.kwargs.swap_remove(name)
}
pub fn remaining_keywords(&mut self) -> impl Iterator<Item = (String, PyObjectRef)> + '_ {
self.kwargs.drain(..)
}
/// Binds these arguments to their respective values.
///
/// If there is an insufficient number of arguments, there are leftover
/// arguments after performing the binding, or if an argument is not of
/// the expected type, a TypeError is raised.
///
/// If the given `FromArgs` includes any conversions, exceptions raised
/// during the conversion will halt the binding and return the error.
pub fn bind<T: FromArgs>(mut self, vm: &VirtualMachine) -> PyResult<T> {
let given_args = self.args.len();
let bound = T::from_args(vm, &mut self)
.map_err(|e| e.into_exception(T::arity(), given_args, vm))?;
if !self.args.is_empty() {
Err(vm.new_type_error(format!(
"Expected at most {} arguments ({} given)",
T::arity().end(),
given_args,
)))
} else if let Some(err) = self.check_kwargs_empty(vm) {
Err(err)
} else {
Ok(bound)
}
}
pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
self.kwargs
.keys()
.next()
.map(|k| vm.new_type_error(format!("Unexpected keyword argument {}", k)))
}
}
/// An error encountered while binding arguments to the parameters of a Python
/// function call.
pub enum ArgumentError {
/// The call provided fewer positional arguments than the function requires.
TooFewArgs,
/// The call provided more positional arguments than the function accepts.
TooManyArgs,
/// The function doesn't accept a keyword argument with the given name.
InvalidKeywordArgument(String),
/// The function require a keyword argument with the given name, but one wasn't provided
RequiredKeywordArgument(String),
/// An exception was raised while binding arguments to the function
/// parameters.
Exception(PyBaseExceptionRef),
}
impl From<PyBaseExceptionRef> for ArgumentError {
fn from(ex: PyBaseExceptionRef) -> Self {
ArgumentError::Exception(ex)
}
}
impl ArgumentError {
fn into_exception(
self,
arity: RangeInclusive<usize>,
num_given: usize,
vm: &VirtualMachine,
) -> PyBaseExceptionRef {
match self {
ArgumentError::TooFewArgs => vm.new_type_error(format!(
"Expected at least {} arguments ({} given)",
arity.start(),
num_given
)),
ArgumentError::TooManyArgs => vm.new_type_error(format!(
"Expected at most {} arguments ({} given)",
arity.end(),
num_given
)),
ArgumentError::InvalidKeywordArgument(name) => {
vm.new_type_error(format!("{} is an invalid keyword argument", name))
}
ArgumentError::RequiredKeywordArgument(name) => {
vm.new_type_error(format!("Required keyqord only argument {}", name))
}
ArgumentError::Exception(ex) => ex,
}
}
}
/// Implemented by any type that can be accepted as a parameter to a built-in
/// function.
///
pub trait FromArgs: Sized {
/// The range of positional arguments permitted by the function signature.
///
/// Returns an empty range if not applicable.
fn arity() -> RangeInclusive<usize> {
0..=0
}
/// Extracts this item from the next argument(s).
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError>;
}
pub trait FromArgOptional {
type Inner: TryFromObject;
fn from_inner(x: Self::Inner) -> Self;
}
impl<T: TryFromObject> FromArgOptional for OptionalArg<T> {
type Inner = T;
fn from_inner(x: T) -> Self {
Self::Present(x)
}
}
impl<T: TryFromObject> FromArgOptional for T {
type Inner = Self;
fn from_inner(x: Self) -> Self {
x
}
}
/// A map of keyword arguments to their values.
///
/// A built-in function with a `KwArgs` parameter is analogous to a Python
/// function with `**kwargs`. All remaining keyword arguments are extracted
/// (and hence the function will permit an arbitrary number of them).
///
/// `KwArgs` optionally accepts a generic type parameter to allow type checks
/// or conversions of each argument.
///
/// Note:
///
/// KwArgs is only for functions that accept arbitrary keyword arguments. For
/// functions that accept only *specific* named arguments, a rust struct with
/// an appropriate FromArgs implementation must be created.
#[derive(Clone)]
pub struct KwArgs<T = PyObjectRef>(IndexMap<String, T>);
impl<T> KwArgs<T> {
pub fn new(map: IndexMap<String, T>) -> Self {
KwArgs(map)
}
pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
self.0.remove(name)
}
}
impl<T> FromIterator<(String, T)> for KwArgs<T> {
fn from_iter<I: IntoIterator<Item = (String, T)>>(iter: I) -> Self {
KwArgs(iter.into_iter().collect())
}
}
impl<T> Default for KwArgs<T> {
fn default() -> Self {
KwArgs(IndexMap::new())
}
}
impl<T> FromArgs for KwArgs<T>
where
T: TryFromObject,
{
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let mut kwargs = IndexMap::new();
for (name, value) in args.remaining_keywords() {
kwargs.insert(name, value.try_into_value(vm)?);
}
Ok(KwArgs(kwargs))
}
}
impl<T> IntoIterator for KwArgs<T> {
type Item = (String, T);
type IntoIter = indexmap::map::IntoIter<String, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
/// A list of positional argument values.
///
/// A built-in function with a `PosArgs` parameter is analogous to a Python
/// function with `*args`. All remaining positional arguments are extracted
/// (and hence the function will permit an arbitrary number of them).
///
/// `PosArgs` optionally accepts a generic type parameter to allow type checks
/// or conversions of each argument.
#[derive(Clone)]
pub struct PosArgs<T = PyObjectRef>(Vec<T>);
impl<T> PosArgs<T> {
pub fn new(args: Vec<T>) -> Self {
Self(args)
}
pub fn into_vec(self) -> Vec<T> {
self.0
}
pub fn iter(&self) -> std::slice::Iter<T> {
self.0.iter()
}
}
impl<T> From<Vec<T>> for PosArgs<T> {
fn from(v: Vec<T>) -> Self {
Self(v)
}
}
impl From<()> for PosArgs<PyObjectRef> {
fn from(_args: ()) -> Self {
Self(Vec::new())
}
}
impl<T> AsRef<[T]> for PosArgs<T> {
fn as_ref(&self) -> &[T] {
&self.0
}
}
impl<T: PyValue> PosArgs<PyRef<T>> {
pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef {
vm.ctx
.new_tuple(self.0.into_iter().map(Into::into).collect())
}
}
impl<T> FromArgs for PosArgs<T>
where
T: TryFromObject,
{
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let mut varargs = Vec::new();
while let Some(value) = args.take_positional() {
varargs.push(value.try_into_value(vm)?);
}
Ok(PosArgs(varargs))
}
}
impl<T> IntoIterator for PosArgs<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T> FromArgs for T
where
T: TryFromObject,
{
fn arity() -> RangeInclusive<usize> {
1..=1
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?;
Ok(value.try_into_value(vm)?)
}
}
/// An argument that may or may not be provided by the caller.
///
/// This style of argument is not possible in pure Python.
#[derive(Debug, result_like::OptionLike, is_macro::Is)]
pub enum OptionalArg<T = PyObjectRef> {
Present(T),
Missing,
}
impl OptionalArg<PyObjectRef> {
pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef {
self.unwrap_or_else(|| vm.ctx.none())
}
}
pub type OptionalOption<T = PyObjectRef> = OptionalArg<Option<T>>;
impl<T> OptionalOption<T> {
#[inline]
pub fn flatten(self) -> Option<T> {
match self {
OptionalArg::Present(Some(value)) => Some(value),
_ => None,
}
}
}
impl<T> FromArgs for OptionalArg<T>
where
T: TryFromObject,
{
fn arity() -> RangeInclusive<usize> {
0..=1
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let r = if let Some(value) = args.take_positional() {
OptionalArg::Present(value.try_into_value(vm)?)
} else {
OptionalArg::Missing
};
Ok(r)
}
}
// For functions that accept no arguments. Implemented explicitly instead of via
// macro below to avoid unused warnings.
impl FromArgs for () {
fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(())
}
}
// A tuple of types that each implement `FromArgs` represents a sequence of
// arguments that can be bound and passed to a built-in function.
//
// Technically, a tuple can contain tuples, which can contain tuples, and so on,
// so this actually represents a tree of values to be bound from arguments, but
// in practice this is only used for the top-level parameters.
macro_rules! tuple_from_py_func_args {
($($T:ident),+) => {
impl<$($T),+> FromArgs for ($($T,)+)
where
$($T: FromArgs),+
{
fn arity() -> RangeInclusive<usize> {
let mut min = 0;
let mut max = 0;
$(
let (start, end) = $T::arity().into_inner();
min += start;
max += end;
)+
min..=max
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(($($T::from_args(vm, args)?,)+))
}
}
};
}
// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind
// up to 7 top-level parameters (note that `PosArgs`, `KwArgs`, nested tuples, etc.
// count as 1, so this should actually be more than enough).
tuple_from_py_func_args!(A);
tuple_from_py_func_args!(A, B);
tuple_from_py_func_args!(A, B, C);
tuple_from_py_func_args!(A, B, C, D);
tuple_from_py_func_args!(A, B, C, D, E);
tuple_from_py_func_args!(A, B, C, D, E, F);
tuple_from_py_func_args!(A, B, C, D, E, F, G);
tuple_from_py_func_args!(A, B, C, D, E, F, G, H);
/// A built-in Python function.
pub type PyNativeFunc = Box<py_dyn_fn!(dyn Fn(&VirtualMachine, FuncArgs) -> PyResult)>;
/// Implemented by types that are or can generate built-in functions.
///
/// This trait is implemented by any function that matches the pattern:
///
/// ```rust,ignore
/// Fn([&self,] [T where T: FromArgs, ...] [, vm: &VirtualMachine])
/// ```
///
/// For example, anything from `Fn()` to `Fn(vm: &VirtualMachine) -> u32` to
/// `Fn(PyIntRef, PyIntRef) -> String` to
/// `Fn(&self, PyStrRef, FooOptions, vm: &VirtualMachine) -> PyResult<PyInt>`
/// is `IntoPyNativeFunc`. If you do want a really general function signature, e.g.
/// to forward the args to another function, you can define a function like
/// `Fn(FuncArgs [, &VirtualMachine]) -> ...`
///
/// Note that the `Kind` type parameter is meaningless and should be considered
/// an implementation detail; if you need to use `IntoPyNativeFunc` as a trait bound
/// just pass an unconstrained generic type, e.g.
/// `fn foo<F, FKind>(f: F) where F: IntoPyNativeFunc<FKind>`
pub trait IntoPyNativeFunc<Kind>: Sized + PyThreadingConstraint + 'static {
fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
/// `IntoPyNativeFunc::into_func()` generates a PyNativeFunc that performs the
/// appropriate type and arity checking, any requested conversions, and then if
/// successful calls the function with the extracted parameters.
fn into_func(self) -> PyNativeFunc {
Box::new(move |vm: &VirtualMachine, args| self.call(vm, args))
}
}
// TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type
// parameter and impl for F where F: for<T, R, VM> PyNativeFuncInternal<T, R, VM>
impl<F, T, R, VM> IntoPyNativeFunc<(T, R, VM)> for F
where
F: PyNativeFuncInternal<T, R, VM>,
{
#[inline(always)]
fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
self.call_(vm, args)
}
}
mod sealed {
use super::*;
pub trait PyNativeFuncInternal<T, R, VM>: Sized + PyThreadingConstraint + 'static {
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
}
}
use sealed::PyNativeFuncInternal;
#[doc(hidden)]
pub struct OwnedParam<T>(PhantomData<T>);
#[doc(hidden)]
pub struct RefParam<T>(PhantomData<T>);
// This is the "magic" that allows rust functions of varying signatures to
// generate native python functions.
//
// Note that this could be done without a macro - it is simply to avoid repetition.
macro_rules! into_py_native_func_tuple {
($(($n:tt, $T:ident)),*) => {
impl<F, $($T,)* R> PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, VirtualMachine> for F
where
F: Fn($($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
(self)($($n,)* vm).to_pyresult(vm)
}
}
impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
where
F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
S: PyValue,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
(self)(&zelf, $($n,)* vm).to_pyresult(vm)
}
}
impl<F, $($T,)* R> PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, ()> for F
where
F: Fn($($T,)*) -> R + PyThreadingConstraint + 'static,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
(self)($($n,)*).to_pyresult(vm)
}
}
impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
where
F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static,
S: PyValue,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
(self)(&zelf, $($n,)*).to_pyresult(vm)
}
}
};
}
into_py_native_func_tuple!();
into_py_native_func_tuple!((v1, T1));
into_py_native_func_tuple!((v1, T1), (v2, T2));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
into_py_native_func_tuple!(
(v1, T1),
(v2, T2),
(v3, T3),
(v4, T4),
(v5, T5),
(v6, T6),
(v7, T7)
);
/// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then
/// test that any of the values contained within the tuples satisfies the predicate. Type parameter
/// T specifies the type that is expected, if the input value is not of that type or a tuple of
/// values of that type, then a TypeError is raised.
pub fn single_or_tuple_any<T, F, M>(
obj: PyObjectRef,
predicate: &F,
message: &M,
vm: &VirtualMachine,
) -> PyResult<bool>
where
T: TryFromObject,
F: Fn(&T) -> PyResult<bool>,
M: Fn(&PyObject) -> String,
{
match T::try_from_object(vm, obj.clone()) {
Ok(single) => (predicate)(&single),
Err(_) => {
let tuple = PyTupleRef::try_from_object(vm, obj.clone())
.map_err(|_| vm.new_type_error((message)(&obj)))?;
for obj in tuple.as_slice().iter() {
if single_or_tuple_any(obj.clone(), predicate, message, vm)? {
return Ok(true);
}
}
Ok(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intonativefunc_noalloc() {
let check_zst = |f: PyNativeFunc| assert_eq!(std::mem::size_of_val(f.as_ref()), 0);
fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 {
1
}
check_zst(py_func.into_func());
let empty_closure = || "foo".to_owned();
check_zst(empty_closure.into_func());
}
}

View File

@@ -1,175 +1,545 @@
use super::IntoFuncArgs;
use crate::{
builtins::{iter::PySequenceIterator, PyDict, PyDictRef},
builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef},
convert::ToPyObject,
protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods},
AsObject, PyObject, PyObjectRef, PyObjectWrap, PyResult, PyValue, TryFromObject,
VirtualMachine,
AsObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use std::{borrow::Borrow, marker::PhantomData};
use indexmap::IndexMap;
use itertools::Itertools;
use std::ops::RangeInclusive;
#[derive(Clone, Debug)]
pub struct ArgCallable {
obj: PyObjectRef,
}
impl ArgCallable {
pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
vm.invoke(&self.obj, args)
pub trait IntoFuncArgs: Sized {
fn into_args(self, vm: &VirtualMachine) -> FuncArgs;
fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
let mut args = self.into_args(vm);
args.prepend_arg(obj);
args
}
}
impl Borrow<PyObject> for ArgCallable {
#[inline(always)]
fn borrow(&self) -> &PyObject {
&self.obj
impl<T> IntoFuncArgs for T
where
T: Into<FuncArgs>,
{
fn into_args(self, _vm: &VirtualMachine) -> FuncArgs {
self.into()
}
}
impl AsRef<PyObject> for ArgCallable {
#[inline(always)]
fn as_ref(&self) -> &PyObject {
&self.obj
}
// A tuple of values that each implement `ToPyObject` represents a sequence of
// arguments that can be bound and passed to a built-in function.
macro_rules! into_func_args_from_tuple {
($(($n:tt, $T:ident)),*) => {
impl<$($T,)*> IntoFuncArgs for ($($T,)*)
where
$($T: ToPyObject,)*
{
#[inline]
fn into_args(self, vm: &VirtualMachine) -> FuncArgs {
let ($($n,)*) = self;
PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into()
}
#[inline]
fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
let ($($n,)*) = self;
PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into()
}
}
};
}
impl PyObjectWrap for ArgCallable {
fn into_object(self) -> PyObjectRef {
self.obj
}
into_func_args_from_tuple!((v1, T1));
into_func_args_from_tuple!((v1, T1), (v2, T2));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
/// The `FuncArgs` struct is one of the most used structs then creating
/// a rust function that can be called from python. It holds both positional
/// arguments, as well as keyword arguments passed to the function.
#[derive(Debug, Default, Clone)]
pub struct FuncArgs {
pub args: Vec<PyObjectRef>,
// sorted map, according to https://www.python.org/dev/peps/pep-0468/
pub kwargs: IndexMap<String, PyObjectRef>,
}
impl TryFromObject for ArgCallable {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
if vm.is_callable(&obj) {
Ok(ArgCallable { obj })
} else {
Err(vm.new_type_error(format!("'{}' object is not callable", obj.class().name())))
/// Conversion from vector of python objects to function arguments.
impl<A> From<A> for FuncArgs
where
A: Into<PosArgs>,
{
fn from(args: A) -> Self {
FuncArgs {
args: args.into().into_vec(),
kwargs: IndexMap::new(),
}
}
}
/// An iterable Python object.
///
/// `ArgIterable` implements `FromArgs` so that a built-in function can accept
/// an object that is required to conform to the Python iterator protocol.
///
/// ArgIterable can optionally perform type checking and conversions on iterated
/// objects using a generic type parameter that implements `TryFromObject`.
pub struct ArgIterable<T = PyObjectRef> {
iterable: PyObjectRef,
iterfn: Option<crate::types::IterFunc>,
_item: PhantomData<T>,
}
impl<T> ArgIterable<T> {
/// Returns an iterator over this sequence of objects.
///
/// This operation may fail if an exception is raised while invoking the
/// `__iter__` method of the iterable object.
pub fn iter<'a>(&self, vm: &'a VirtualMachine) -> PyResult<PyIterIter<'a, T>> {
let iter = PyIter::new(match self.iterfn {
Some(f) => f(self.iterable.clone(), vm)?,
None => PySequenceIterator::new(self.iterable.clone(), vm)?.into_object(vm),
});
iter.into_iter(vm)
impl From<KwArgs> for FuncArgs {
fn from(kwargs: KwArgs) -> Self {
FuncArgs {
args: Vec::new(),
kwargs: kwargs.0,
}
}
}
impl<T> TryFromObject for ArgIterable<T>
impl FromArgs for FuncArgs {
fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(std::mem::take(args))
}
}
impl FuncArgs {
pub fn new<A, K>(args: A, kwargs: K) -> Self
where
A: Into<PosArgs>,
K: Into<KwArgs>,
{
let PosArgs(args) = args.into();
let KwArgs(kwargs) = kwargs.into();
Self { args, kwargs }
}
pub fn with_kwargs_names<A, KW>(mut args: A, kwarg_names: KW) -> Self
where
A: ExactSizeIterator<Item = PyObjectRef>,
KW: ExactSizeIterator<Item = String>,
{
// last `kwarg_names.len()` elements of args in order of appearance in the call signature
let total_argc = args.len();
let kwargc = kwarg_names.len();
let posargc = total_argc - kwargc;
let posargs = args.by_ref().take(posargc).collect();
let kwargs = kwarg_names.zip_eq(args).collect::<IndexMap<_, _>>();
FuncArgs {
args: posargs,
kwargs,
}
}
pub fn prepend_arg(&mut self, item: PyObjectRef) {
self.args.reserve_exact(1);
self.args.insert(0, item)
}
pub fn shift(&mut self) -> PyObjectRef {
self.args.remove(0)
}
pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef {
self.kwargs
.get(key)
.cloned()
.unwrap_or_else(|| default.clone())
}
pub fn get_optional_kwarg(&self, key: &str) -> Option<PyObjectRef> {
self.kwargs.get(key).cloned()
}
pub fn get_optional_kwarg_with_type(
&self,
key: &str,
ty: PyTypeRef,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
match self.get_optional_kwarg(key) {
Some(kwarg) => {
if kwarg.fast_isinstance(&ty) {
Ok(Some(kwarg))
} else {
let expected_ty_name = &ty.name();
let kwarg_class = kwarg.class();
let actual_ty_name = &kwarg_class.name();
Err(vm.new_type_error(format!(
"argument of type {} is required for named parameter `{}` (got: {})",
expected_ty_name, key, actual_ty_name
)))
}
}
None => Ok(None),
}
}
pub fn take_positional(&mut self) -> Option<PyObjectRef> {
if self.args.is_empty() {
None
} else {
Some(self.args.remove(0))
}
}
pub fn take_positional_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
self.take_positional().or_else(|| self.take_keyword(name))
}
pub fn take_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
self.kwargs.swap_remove(name)
}
pub fn remaining_keywords(&mut self) -> impl Iterator<Item = (String, PyObjectRef)> + '_ {
self.kwargs.drain(..)
}
/// Binds these arguments to their respective values.
///
/// If there is an insufficient number of arguments, there are leftover
/// arguments after performing the binding, or if an argument is not of
/// the expected type, a TypeError is raised.
///
/// If the given `FromArgs` includes any conversions, exceptions raised
/// during the conversion will halt the binding and return the error.
pub fn bind<T: FromArgs>(mut self, vm: &VirtualMachine) -> PyResult<T> {
let given_args = self.args.len();
let bound = T::from_args(vm, &mut self)
.map_err(|e| e.into_exception(T::arity(), given_args, vm))?;
if !self.args.is_empty() {
Err(vm.new_type_error(format!(
"Expected at most {} arguments ({} given)",
T::arity().end(),
given_args,
)))
} else if let Some(err) = self.check_kwargs_empty(vm) {
Err(err)
} else {
Ok(bound)
}
}
pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
self.kwargs
.keys()
.next()
.map(|k| vm.new_type_error(format!("Unexpected keyword argument {}", k)))
}
}
/// An error encountered while binding arguments to the parameters of a Python
/// function call.
pub enum ArgumentError {
/// The call provided fewer positional arguments than the function requires.
TooFewArgs,
/// The call provided more positional arguments than the function accepts.
TooManyArgs,
/// The function doesn't accept a keyword argument with the given name.
InvalidKeywordArgument(String),
/// The function require a keyword argument with the given name, but one wasn't provided
RequiredKeywordArgument(String),
/// An exception was raised while binding arguments to the function
/// parameters.
Exception(PyBaseExceptionRef),
}
impl From<PyBaseExceptionRef> for ArgumentError {
fn from(ex: PyBaseExceptionRef) -> Self {
ArgumentError::Exception(ex)
}
}
impl ArgumentError {
fn into_exception(
self,
arity: RangeInclusive<usize>,
num_given: usize,
vm: &VirtualMachine,
) -> PyBaseExceptionRef {
match self {
ArgumentError::TooFewArgs => vm.new_type_error(format!(
"Expected at least {} arguments ({} given)",
arity.start(),
num_given
)),
ArgumentError::TooManyArgs => vm.new_type_error(format!(
"Expected at most {} arguments ({} given)",
arity.end(),
num_given
)),
ArgumentError::InvalidKeywordArgument(name) => {
vm.new_type_error(format!("{} is an invalid keyword argument", name))
}
ArgumentError::RequiredKeywordArgument(name) => {
vm.new_type_error(format!("Required keyqord only argument {}", name))
}
ArgumentError::Exception(ex) => ex,
}
}
}
/// Implemented by any type that can be accepted as a parameter to a built-in
/// function.
///
pub trait FromArgs: Sized {
/// The range of positional arguments permitted by the function signature.
///
/// Returns an empty range if not applicable.
fn arity() -> RangeInclusive<usize> {
0..=0
}
/// Extracts this item from the next argument(s).
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError>;
}
pub trait FromArgOptional {
type Inner: TryFromObject;
fn from_inner(x: Self::Inner) -> Self;
}
impl<T: TryFromObject> FromArgOptional for OptionalArg<T> {
type Inner = T;
fn from_inner(x: T) -> Self {
Self::Present(x)
}
}
impl<T: TryFromObject> FromArgOptional for T {
type Inner = Self;
fn from_inner(x: Self) -> Self {
x
}
}
/// A map of keyword arguments to their values.
///
/// A built-in function with a `KwArgs` parameter is analogous to a Python
/// function with `**kwargs`. All remaining keyword arguments are extracted
/// (and hence the function will permit an arbitrary number of them).
///
/// `KwArgs` optionally accepts a generic type parameter to allow type checks
/// or conversions of each argument.
///
/// Note:
///
/// KwArgs is only for functions that accept arbitrary keyword arguments. For
/// functions that accept only *specific* named arguments, a rust struct with
/// an appropriate FromArgs implementation must be created.
#[derive(Clone)]
pub struct KwArgs<T = PyObjectRef>(IndexMap<String, T>);
impl<T> KwArgs<T> {
pub fn new(map: IndexMap<String, T>) -> Self {
KwArgs(map)
}
pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
self.0.remove(name)
}
}
impl<T> FromIterator<(String, T)> for KwArgs<T> {
fn from_iter<I: IntoIterator<Item = (String, T)>>(iter: I) -> Self {
KwArgs(iter.into_iter().collect())
}
}
impl<T> Default for KwArgs<T> {
fn default() -> Self {
KwArgs(IndexMap::new())
}
}
impl<T> FromArgs for KwArgs<T>
where
T: TryFromObject,
{
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let iterfn;
{
let cls = obj.class();
iterfn = cls.mro_find_map(|x| x.slots.iter.load());
if iterfn.is_none() && !cls.has_attr("__getitem__") {
return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name())));
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let mut kwargs = IndexMap::new();
for (name, value) in args.remaining_keywords() {
kwargs.insert(name, value.try_into_value(vm)?);
}
Ok(Self {
iterable: obj,
iterfn,
_item: PhantomData,
})
Ok(KwArgs(kwargs))
}
}
impl<T> IntoIterator for KwArgs<T> {
type Item = (String, T);
type IntoIter = indexmap::map::IntoIter<String, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
/// A list of positional argument values.
///
/// A built-in function with a `PosArgs` parameter is analogous to a Python
/// function with `*args`. All remaining positional arguments are extracted
/// (and hence the function will permit an arbitrary number of them).
///
/// `PosArgs` optionally accepts a generic type parameter to allow type checks
/// or conversions of each argument.
#[derive(Clone)]
pub struct ArgMapping {
obj: PyObjectRef,
mapping_methods: PyMappingMethods,
}
pub struct PosArgs<T = PyObjectRef>(Vec<T>);
impl ArgMapping {
pub fn from_dict_exact(dict: PyDictRef) -> Self {
Self {
obj: dict.into(),
mapping_methods: PyDict::MAPPING_METHODS,
}
impl<T> PosArgs<T> {
pub fn new(args: Vec<T>) -> Self {
Self(args)
}
pub fn mapping(&self) -> PyMapping {
PyMapping::with_methods(&self.obj, self.mapping_methods)
}
}
impl Borrow<PyObject> for ArgMapping {
#[inline(always)]
fn borrow(&self) -> &PyObject {
&self.obj
}
}
impl AsRef<PyObject> for ArgMapping {
#[inline(always)]
fn as_ref(&self) -> &PyObject {
&self.obj
}
}
impl PyObjectWrap for ArgMapping {
#[inline(always)]
fn into_object(self) -> PyObjectRef {
self.obj
}
}
impl ToPyObject for ArgMapping {
#[inline(always)]
fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
self.obj
}
}
impl TryFromObject for ArgMapping {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let mapping = PyMapping::try_protocol(&obj, vm)?;
let mapping_methods = *mapping.methods(vm);
Ok(Self {
obj,
mapping_methods,
})
}
}
// this is not strictly related to PySequence protocol.
#[derive(Clone)]
pub struct ArgSequence<T = PyObjectRef>(Vec<T>);
impl<T> ArgSequence<T> {
pub fn into_vec(self) -> Vec<T> {
self.0
}
pub fn as_slice(&self) -> &[T] {
pub fn iter(&self) -> std::slice::Iter<T> {
self.0.iter()
}
}
impl<T> From<Vec<T>> for PosArgs<T> {
fn from(v: Vec<T>) -> Self {
Self(v)
}
}
impl From<()> for PosArgs<PyObjectRef> {
fn from(_args: ()) -> Self {
Self(Vec::new())
}
}
impl<T> AsRef<[T]> for PosArgs<T> {
fn as_ref(&self) -> &[T] {
&self.0
}
}
impl<T: TryFromObject> TryFromObject for ArgSequence<T> {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
obj.try_to_value(vm).map(Self)
impl<T: PyValue> PosArgs<PyRef<T>> {
pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef {
vm.ctx
.new_tuple(self.0.into_iter().map(Into::into).collect())
}
}
impl<T> FromArgs for PosArgs<T>
where
T: TryFromObject,
{
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let mut varargs = Vec::new();
while let Some(value) = args.take_positional() {
varargs.push(value.try_into_value(vm)?);
}
Ok(PosArgs(varargs))
}
}
impl<T> IntoIterator for PosArgs<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T> FromArgs for T
where
T: TryFromObject,
{
fn arity() -> RangeInclusive<usize> {
1..=1
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?;
Ok(value.try_into_value(vm)?)
}
}
/// An argument that may or may not be provided by the caller.
///
/// This style of argument is not possible in pure Python.
#[derive(Debug, result_like::OptionLike, is_macro::Is)]
pub enum OptionalArg<T = PyObjectRef> {
Present(T),
Missing,
}
impl OptionalArg<PyObjectRef> {
pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef {
self.unwrap_or_else(|| vm.ctx.none())
}
}
pub type OptionalOption<T = PyObjectRef> = OptionalArg<Option<T>>;
impl<T> OptionalOption<T> {
#[inline]
pub fn flatten(self) -> Option<T> {
match self {
OptionalArg::Present(Some(value)) => Some(value),
_ => None,
}
}
}
impl<T> FromArgs for OptionalArg<T>
where
T: TryFromObject,
{
fn arity() -> RangeInclusive<usize> {
0..=1
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let r = if let Some(value) = args.take_positional() {
OptionalArg::Present(value.try_into_value(vm)?)
} else {
OptionalArg::Missing
};
Ok(r)
}
}
// For functions that accept no arguments. Implemented explicitly instead of via
// macro below to avoid unused warnings.
impl FromArgs for () {
fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(())
}
}
// A tuple of types that each implement `FromArgs` represents a sequence of
// arguments that can be bound and passed to a built-in function.
//
// Technically, a tuple can contain tuples, which can contain tuples, and so on,
// so this actually represents a tree of values to be bound from arguments, but
// in practice this is only used for the top-level parameters.
macro_rules! tuple_from_py_func_args {
($($T:ident),+) => {
impl<$($T),+> FromArgs for ($($T,)+)
where
$($T: FromArgs),+
{
fn arity() -> RangeInclusive<usize> {
let mut min = 0;
let mut max = 0;
$(
let (start, end) = $T::arity().into_inner();
min += start;
max += end;
)+
min..=max
}
fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
Ok(($($T::from_args(vm, args)?,)+))
}
}
};
}
// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind
// up to 7 top-level parameters (note that `PosArgs`, `KwArgs`, nested tuples, etc.
// count as 1, so this should actually be more than enough).
tuple_from_py_func_args!(A);
tuple_from_py_func_args!(A, B);
tuple_from_py_func_args!(A, B, C);
tuple_from_py_func_args!(A, B, C, D);
tuple_from_py_func_args!(A, B, C, D, E);
tuple_from_py_func_args!(A, B, C, D, E, F);
tuple_from_py_func_args!(A, B, C, D, E, F, G);
tuple_from_py_func_args!(A, B, C, D, E, F, G, H);

157
vm/src/function/builtin.rs Normal file
View File

@@ -0,0 +1,157 @@
use super::{FromArgs, FuncArgs};
use crate::{
convert::ToPyResult, pyobject::PyThreadingConstraint, PyRef, PyResult, PyValue, VirtualMachine,
};
use std::marker::PhantomData;
/// A built-in Python function.
pub type PyNativeFunc = Box<py_dyn_fn!(dyn Fn(&VirtualMachine, FuncArgs) -> PyResult)>;
/// Implemented by types that are or can generate built-in functions.
///
/// This trait is implemented by any function that matches the pattern:
///
/// ```rust,ignore
/// Fn([&self,] [T where T: FromArgs, ...] [, vm: &VirtualMachine])
/// ```
///
/// For example, anything from `Fn()` to `Fn(vm: &VirtualMachine) -> u32` to
/// `Fn(PyIntRef, PyIntRef) -> String` to
/// `Fn(&self, PyStrRef, FooOptions, vm: &VirtualMachine) -> PyResult<PyInt>`
/// is `IntoPyNativeFunc`. If you do want a really general function signature, e.g.
/// to forward the args to another function, you can define a function like
/// `Fn(FuncArgs [, &VirtualMachine]) -> ...`
///
/// Note that the `Kind` type parameter is meaningless and should be considered
/// an implementation detail; if you need to use `IntoPyNativeFunc` as a trait bound
/// just pass an unconstrained generic type, e.g.
/// `fn foo<F, FKind>(f: F) where F: IntoPyNativeFunc<FKind>`
pub trait IntoPyNativeFunc<Kind>: Sized + PyThreadingConstraint + 'static {
fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
/// `IntoPyNativeFunc::into_func()` generates a PyNativeFunc that performs the
/// appropriate type and arity checking, any requested conversions, and then if
/// successful calls the function with the extracted parameters.
fn into_func(self) -> PyNativeFunc {
Box::new(move |vm: &VirtualMachine, args| self.call(vm, args))
}
}
// TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type
// parameter and impl for F where F: for<T, R, VM> PyNativeFuncInternal<T, R, VM>
impl<F, T, R, VM> IntoPyNativeFunc<(T, R, VM)> for F
where
F: PyNativeFuncInternal<T, R, VM>,
{
#[inline(always)]
fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
self.call_(vm, args)
}
}
mod sealed {
use super::*;
pub trait PyNativeFuncInternal<T, R, VM>: Sized + PyThreadingConstraint + 'static {
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
}
}
use sealed::PyNativeFuncInternal;
#[doc(hidden)]
pub struct OwnedParam<T>(PhantomData<T>);
#[doc(hidden)]
pub struct RefParam<T>(PhantomData<T>);
// This is the "magic" that allows rust functions of varying signatures to
// generate native python functions.
//
// Note that this could be done without a macro - it is simply to avoid repetition.
macro_rules! into_py_native_func_tuple {
($(($n:tt, $T:ident)),*) => {
impl<F, $($T,)* R> PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, VirtualMachine> for F
where
F: Fn($($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
(self)($($n,)* vm).to_pyresult(vm)
}
}
impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
where
F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
S: PyValue,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
(self)(&zelf, $($n,)* vm).to_pyresult(vm)
}
}
impl<F, $($T,)* R> PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, ()> for F
where
F: Fn($($T,)*) -> R + PyThreadingConstraint + 'static,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
(self)($($n,)*).to_pyresult(vm)
}
}
impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
where
F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static,
S: PyValue,
$($T: FromArgs,)*
R: ToPyResult,
{
fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
(self)(&zelf, $($n,)*).to_pyresult(vm)
}
}
};
}
into_py_native_func_tuple!();
into_py_native_func_tuple!((v1, T1));
into_py_native_func_tuple!((v1, T1), (v2, T2));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
into_py_native_func_tuple!(
(v1, T1),
(v2, T2),
(v3, T3),
(v4, T4),
(v5, T5),
(v6, T6),
(v7, T7)
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intonativefunc_noalloc() {
let check_zst = |f: PyNativeFunc| assert_eq!(std::mem::size_of_val(f.as_ref()), 0);
fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 {
1
}
check_zst(py_func.into_func());
let empty_closure = || "foo".to_owned();
check_zst(empty_closure.into_func());
}
}

50
vm/src/function/mod.rs Normal file
View File

@@ -0,0 +1,50 @@
mod argument;
mod arithmetic;
mod buffer;
mod builtin;
mod number;
mod protocol;
pub use argument::{
ArgumentError, FromArgOptional, FromArgs, FuncArgs, IntoFuncArgs, KwArgs, OptionalArg,
OptionalOption, PosArgs,
};
pub use arithmetic::{PyArithmeticValue, PyComparisonValue};
pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike};
pub use builtin::{IntoPyNativeFunc, OwnedParam, PyNativeFunc, RefParam};
pub use number::{ArgIntoBool, ArgIntoComplex, ArgIntoFloat};
pub use protocol::{ArgCallable, ArgIterable, ArgMapping, ArgSequence};
use crate::{
builtins::PyTupleRef, convert::TryFromObject, PyObject, PyObjectRef, PyResult, VirtualMachine,
};
/// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then
/// test that any of the values contained within the tuples satisfies the predicate. Type parameter
/// T specifies the type that is expected, if the input value is not of that type or a tuple of
/// values of that type, then a TypeError is raised.
pub fn single_or_tuple_any<T, F, M>(
obj: PyObjectRef,
predicate: &F,
message: &M,
vm: &VirtualMachine,
) -> PyResult<bool>
where
T: TryFromObject,
F: Fn(&T) -> PyResult<bool>,
M: Fn(&PyObject) -> String,
{
match T::try_from_object(vm, obj.clone()) {
Ok(single) => (predicate)(&single),
Err(_) => {
let tuple = PyTupleRef::try_from_object(vm, obj.clone())
.map_err(|_| vm.new_type_error((message)(&obj)))?;
for obj in tuple.as_slice().iter() {
if single_or_tuple_any(obj.clone(), predicate, message, vm)? {
return Ok(true);
}
}
Ok(false)
}
}
}

180
vm/src/function/protocol.rs Normal file
View File

@@ -0,0 +1,180 @@
use super::IntoFuncArgs;
use crate::{
builtins::{iter::PySequenceIterator, PyDict, PyDictRef},
convert::ToPyObject,
protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods},
AsObject, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use std::{borrow::Borrow, marker::PhantomData};
#[derive(Clone, Debug)]
pub struct ArgCallable {
obj: PyObjectRef,
}
impl ArgCallable {
#[inline(always)]
pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
vm.invoke(&self.obj, args)
}
}
impl Borrow<PyObject> for ArgCallable {
#[inline(always)]
fn borrow(&self) -> &PyObject {
&self.obj
}
}
impl AsRef<PyObject> for ArgCallable {
#[inline(always)]
fn as_ref(&self) -> &PyObject {
&self.obj
}
}
impl From<ArgCallable> for PyObjectRef {
#[inline(always)]
fn from(value: ArgCallable) -> PyObjectRef {
value.obj
}
}
impl TryFromObject for ArgCallable {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
if vm.is_callable(&obj) {
Ok(ArgCallable { obj })
} else {
Err(vm.new_type_error(format!("'{}' object is not callable", obj.class().name())))
}
}
}
/// An iterable Python object.
///
/// `ArgIterable` implements `FromArgs` so that a built-in function can accept
/// an object that is required to conform to the Python iterator protocol.
///
/// ArgIterable can optionally perform type checking and conversions on iterated
/// objects using a generic type parameter that implements `TryFromObject`.
pub struct ArgIterable<T = PyObjectRef> {
iterable: PyObjectRef,
iterfn: Option<crate::types::IterFunc>,
_item: PhantomData<T>,
}
impl<T> ArgIterable<T> {
/// Returns an iterator over this sequence of objects.
///
/// This operation may fail if an exception is raised while invoking the
/// `__iter__` method of the iterable object.
pub fn iter<'a>(&self, vm: &'a VirtualMachine) -> PyResult<PyIterIter<'a, T>> {
let iter = PyIter::new(match self.iterfn {
Some(f) => f(self.iterable.clone(), vm)?,
None => PySequenceIterator::new(self.iterable.clone(), vm)?.into_pyobject(vm),
});
iter.into_iter(vm)
}
}
impl<T> TryFromObject for ArgIterable<T>
where
T: TryFromObject,
{
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let iterfn;
{
let cls = obj.class();
iterfn = cls.mro_find_map(|x| x.slots.iter.load());
if iterfn.is_none() && !cls.has_attr("__getitem__") {
return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name())));
}
}
Ok(Self {
iterable: obj,
iterfn,
_item: PhantomData,
})
}
}
#[derive(Clone)]
pub struct ArgMapping {
obj: PyObjectRef,
mapping_methods: PyMappingMethods,
}
impl ArgMapping {
#[inline(always)]
pub fn from_dict_exact(dict: PyDictRef) -> Self {
Self {
obj: dict.into(),
mapping_methods: PyDict::MAPPING_METHODS,
}
}
#[inline(always)]
pub fn mapping(&self) -> PyMapping {
PyMapping::with_methods(&self.obj, self.mapping_methods)
}
}
impl Borrow<PyObject> for ArgMapping {
#[inline(always)]
fn borrow(&self) -> &PyObject {
&self.obj
}
}
impl AsRef<PyObject> for ArgMapping {
#[inline(always)]
fn as_ref(&self) -> &PyObject {
&self.obj
}
}
impl From<ArgMapping> for PyObjectRef {
#[inline(always)]
fn from(value: ArgMapping) -> PyObjectRef {
value.obj
}
}
impl ToPyObject for ArgMapping {
#[inline(always)]
fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
self.obj
}
}
impl TryFromObject for ArgMapping {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let mapping = PyMapping::try_protocol(&obj, vm)?;
let mapping_methods = *mapping.methods(vm);
Ok(Self {
obj,
mapping_methods,
})
}
}
// this is not strictly related to PySequence protocol.
#[derive(Clone)]
pub struct ArgSequence<T = PyObjectRef>(Vec<T>);
impl<T> ArgSequence<T> {
#[inline(always)]
pub fn into_vec(self) -> Vec<T> {
self.0
}
#[inline(always)]
pub fn as_slice(&self) -> &[T] {
&self.0
}
}
impl<T: TryFromObject> TryFromObject for ArgSequence<T> {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
obj.try_to_value(vm).map(Self)
}
}

View File

@@ -89,7 +89,7 @@ mod pyobject {
pub use self::convert::{TryFromBorrowedObject, TryFromObject};
// pyobject items
pub use self::pyobject::{
AsObject, PyContext, PyMethod, PyObjectWrap, PyRefExact, PyResult, PyStructSequence, PyValue,
AsObject, PyContext, PyMethod, PyRefExact, PyResult, PyStructSequence, PyValue,
};
// pyobjectrc items
pub use self::pyobject::{PyObject, PyObjectRef, PyObjectView, PyObjectWeak, PyRef, PyWeakRef};

View File

@@ -72,7 +72,7 @@ macro_rules! py_namespace {
/// use rustpython_vm::{PyValue};
///
/// # rustpython_vm::Interpreter::default().enter(|vm| {
/// let obj = PyInt::from(0).into_object(vm);
/// let obj = PyInt::from(0).into_pyobject(vm);
/// assert_eq!(
/// "int",
/// match_class!(match obj {
@@ -96,7 +96,7 @@ macro_rules! py_namespace {
/// use rustpython_vm::{ PyValue};
///
/// # rustpython_vm::Interpreter::default().enter(|vm| {
/// let obj = PyInt::from(0).into_object(vm);
/// let obj = PyInt::from(0).into_pyobject(vm);
///
/// let int_value = match_class!(match obj {
/// i @ PyInt => i.as_bigint().clone(),

View File

@@ -9,8 +9,8 @@ use crate::{
pyobject::PyObjectPayload,
sliceable::wrap_index,
types::{Constructor, Unconstructible},
AsObject, PyObject, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult, PyValue,
TryFromBorrowedObject, VirtualMachine,
AsObject, PyObject, PyObjectRef, PyObjectView, PyRef, PyResult, PyValue, TryFromBorrowedObject,
VirtualMachine,
};
use itertools::Itertools;
use std::{borrow::Cow, fmt::Debug, ops::Range};
@@ -65,7 +65,7 @@ impl PyBuffer {
pub fn from_byte_vector(bytes: Vec<u8>, vm: &VirtualMachine) -> Self {
let bytes_len = bytes.len();
PyBuffer::new(
PyValue::into_object(VecBuffer::from(bytes), vm),
PyValue::into_pyobject(VecBuffer::from(bytes), vm),
BufferDescriptor::simple(bytes_len, true),
&VEC_BUFFER_METHODS,
)
@@ -421,14 +421,14 @@ impl PyRef<VecBuffer> {
pub fn into_pybuffer(self, readonly: bool) -> PyBuffer {
let len = self.data.lock().len();
PyBuffer::new(
self.into_object(),
self.into(),
BufferDescriptor::simple(len, readonly),
&VEC_BUFFER_METHODS,
)
}
pub fn into_pybuffer_with_descriptor(self, desc: BufferDescriptor) -> PyBuffer {
PyBuffer::new(self.into_object(), desc, &VEC_BUFFER_METHODS)
PyBuffer::new(self.into(), desc, &VEC_BUFFER_METHODS)
}
}

View File

@@ -1,8 +1,7 @@
use crate::{
builtins::iter::PySequenceIterator,
convert::{ToPyObject, ToPyResult},
AsObject, PyObject, PyObjectRef, PyObjectWrap, PyResult, PyValue, TryFromObject,
VirtualMachine,
AsObject, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use std::borrow::Borrow;
use std::ops::Deref;
@@ -70,9 +69,9 @@ impl PyIter<PyObjectRef> {
}
}
impl PyObjectWrap for PyIter<PyObjectRef> {
fn into_object(self) -> PyObjectRef {
self.0
impl From<PyIter<PyObjectRef>> for PyObjectRef {
fn from(value: PyIter<PyObjectRef>) -> PyObjectRef {
value.0
}
}
@@ -134,7 +133,7 @@ impl TryFromObject for PyIter<PyObjectRef> {
)))
}
} else if let Ok(seq_iter) = PySequenceIterator::new(iter_target.clone(), vm) {
Ok(Self(seq_iter.into_object(vm)))
Ok(Self(seq_iter.into_pyobject(vm)))
} else {
Err(vm.new_type_error(format!(
"'{}' object is not iterable",

View File

@@ -26,6 +26,7 @@ pub struct PyMapping<'a> {
}
impl<'a> From<&'a PyObject> for PyMapping<'a> {
#[inline(always)]
fn from(obj: &'a PyObject) -> Self {
Self {
obj,
@@ -35,12 +36,14 @@ impl<'a> From<&'a PyObject> for PyMapping<'a> {
}
impl AsRef<PyObject> for PyMapping<'_> {
#[inline(always)]
fn as_ref(&self) -> &PyObject {
self.obj
}
}
impl<'a> PyMapping<'a> {
#[inline(always)]
pub fn with_methods(obj: &'a PyObject, methods: PyMappingMethods) -> Self {
Self {
obj,
@@ -60,6 +63,7 @@ impl<'a> PyMapping<'a> {
impl PyMapping<'_> {
// PyMapping::Check
#[inline]
pub fn check(&self, vm: &VirtualMachine) -> bool {
self.methods(vm).subscript.is_some()
}

View File

@@ -229,7 +229,7 @@ impl PySequence<'_> {
stop: stop.to_pyobject(vm),
step: None,
};
mapping.subscript(&slice.into_object(vm), vm)
mapping.subscript(&slice.into_pyobject(vm), vm)
} else {
Err(vm.new_type_error(format!("'{}' object is unsliceable", self.obj.class())))
}
@@ -249,7 +249,7 @@ impl PySequence<'_> {
stop: stop.to_pyobject(vm),
step: None,
};
f(&mapping, &slice.into_object(vm), value, vm)
f(&mapping, &slice.into_pyobject(vm), value, vm)
} else {
Err(vm.new_type_error(format!(
"'{}' object doesn't support slice {}",

View File

@@ -526,7 +526,7 @@ where
{
#[inline(always)]
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
PyValue::into_object(self, vm)
PyValue::into_pyobject(self, vm)
}
}
@@ -564,7 +564,7 @@ pub trait PyValue: fmt::Debug + PyThreadingConstraint + Sized + 'static {
fn class(vm: &VirtualMachine) -> &PyTypeRef;
#[inline]
fn into_object(self, vm: &VirtualMachine) -> PyObjectRef {
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
self.into_ref(vm).into()
}
@@ -685,23 +685,6 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
}
}
pub trait PyObjectWrap
where
Self: AsObject,
{
fn into_object(self) -> PyObjectRef;
}
impl<T> From<T> for PyObjectRef
where
T: PyObjectWrap,
{
#[inline(always)]
fn from(py_ref: T) -> Self {
py_ref.into_object()
}
}
#[derive(Debug)]
pub enum PyMethod {
Function {

View File

@@ -3,7 +3,7 @@ use crate::common::linked_list::{Link, LinkedList, Pointers};
use crate::common::lock::{PyMutex, PyMutexGuard, PyRwLock};
use crate::common::refcount::RefCount;
use crate::{
_pyobject::{AsObject, PyObjectPayload, PyObjectWrap, PyResult},
_pyobject::{AsObject, PyObjectPayload, PyResult},
builtins::{PyBaseExceptionRef, PyDictRef, PyTypeRef},
vm::VirtualMachine,
};
@@ -792,10 +792,10 @@ impl Borrow<PyObject> for PyObjectWeak {
}
}
impl PyObjectWrap for PyObjectWeak {
impl From<PyObjectWeak> for PyRef<PyWeak> {
#[inline(always)]
fn into_object(self) -> PyObjectRef {
self.weak.into_object()
fn from(value: PyObjectWeak) -> Self {
value.weak
}
}
@@ -999,13 +999,13 @@ where
}
}
impl<T> PyObjectWrap for PyRef<T>
impl<T> From<PyRef<T>> for PyObjectRef
where
T: PyObjectPayload,
{
#[inline]
fn into_object(self) -> PyObjectRef {
let me = ManuallyDrop::new(self);
fn from(value: PyRef<T>) -> Self {
let me = ManuallyDrop::new(value);
PyObjectRef { ptr: me.ptr.cast() }
}
}

View File

@@ -33,8 +33,7 @@ mod builtins {
stdlib::sys,
types::PyComparisonOp,
utils::Either,
AsObject, PyObject, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue, TryFromObject,
VirtualMachine,
AsObject, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use num_traits::{Signed, ToPrimitive, Zero};
@@ -433,8 +432,8 @@ mod builtins {
}
#[pyfunction]
fn locals(vm: &VirtualMachine) -> PyResult {
vm.current_locals().map(|x| x.into_object())
fn locals(vm: &VirtualMachine) -> PyResult<ArgMapping> {
vm.current_locals()
}
fn min_or_max(
@@ -690,7 +689,7 @@ mod builtins {
})?;
let len = obj.length(vm)?;
let obj_iterator = PyReverseSequenceIterator::new(obj, len);
Ok(obj_iterator.into_object(vm))
Ok(obj_iterator.into_pyobject(vm))
}
}
@@ -793,7 +792,7 @@ mod builtins {
vm.new_type_error("vars() argument must have __dict__ attribute".to_owned())
})
} else {
Ok(vm.current_locals()?.into_object())
Ok(vm.current_locals()?.into())
}
}

View File

@@ -571,7 +571,7 @@ mod _collections {
impl Iterable for PyDeque {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyDequeIterator::new(zelf).into_object(vm))
Ok(PyDequeIterator::new(zelf).into_pyobject(vm))
}
}

View File

@@ -114,7 +114,7 @@ mod _io {
types::{Constructor, Destructor, IterNext, Iterable},
utils::Either,
vm::{ReprGuard, VirtualMachine},
AsObject, PyContext, PyObject, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue,
AsObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue,
TryFromBorrowedObject, TryFromObject,
};
use bstr::ByteSlice;
@@ -3313,7 +3313,7 @@ mod _io {
fn getbuffer(self, vm: &VirtualMachine) -> PyResult<PyMemoryView> {
let len = self.buffer.read().cursor.get_ref().len();
let buffer = PyBuffer::new(
self.into_object(),
self.into(),
BufferDescriptor::simple(len, false),
&BYTES_IO_BUFFER_METHODS,
);

View File

@@ -484,7 +484,7 @@ mod _sre {
/* get segment following last match */
sublist.push(slice_drive(&state.string, last_pos, state.end, vm));
let list = PyList::from(sublist).into_object(vm);
let list = PyList::from(sublist).into_pyobject(vm);
let join_type: PyObjectRef = if zelf.isbytes {
vm.ctx.new_bytes(vec![]).into()

View File

@@ -143,7 +143,7 @@ mod symtable {
.symtable
.sub_tables
.iter()
.map(|t| to_py_symbol_table(t.clone()).into_object(vm))
.map(|t| to_py_symbol_table(t.clone()).into_pyobject(vm))
.collect();
Ok(children)
}
@@ -231,7 +231,7 @@ mod symtable {
let namespaces = self
.namespaces
.iter()
.map(|table| to_py_symbol_table(table.clone()).into_object(vm))
.map(|table| to_py_symbol_table(table.clone()).into_pyobject(vm))
.collect();
Ok(namespaces)
}

View File

@@ -9,7 +9,7 @@ pub(crate) use _weakref::make_module;
#[pymodule]
mod _weakref {
use crate::builtins::{PyDictRef, PyTypeRef, PyWeak};
use crate::{PyObjectRef, PyObjectWrap, PyResult, VirtualMachine};
use crate::{PyObjectRef, PyRef, PyResult, VirtualMachine};
#[pyattr(name = "ref")]
fn ref_(vm: &VirtualMachine) -> PyTypeRef {
@@ -40,7 +40,7 @@ mod _weakref {
#[pyfunction]
fn getweakrefs(obj: PyObjectRef) -> Vec<PyObjectRef> {
match obj.get_weak_references() {
Some(v) => v.into_iter().map(|weak| weak.into_object()).collect(),
Some(v) => v.into_iter().map(|weak| PyRef::from(weak).into()).collect(),
None => vec![],
}
}

View File

@@ -91,6 +91,7 @@ pub struct TypeZoo {
}
impl TypeZoo {
#[cold]
pub(crate) fn init() -> Self {
let (type_type, object_type, weakref_type) = crate::pyobject::init_type_hierarchy();
Self {
@@ -179,6 +180,7 @@ impl TypeZoo {
}
/// Fill attributes of builtin types.
#[cold]
pub(crate) fn extend(context: &PyContext) {
pytype::init(context);
object::init(context);

View File

@@ -1,7 +1,7 @@
use crate::{
builtins::{PyFloat, PyStr},
convert::{ToPyException, ToPyObject},
AsObject, PyObject, PyObjectRef, PyObjectWrap, PyResult, TryFromObject, VirtualMachine,
AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
};
use num_traits::ToPrimitive;
use std::borrow::Borrow;
@@ -31,12 +31,12 @@ impl<A: AsRef<PyObject>, B: AsRef<PyObject>> AsRef<PyObject> for Either<A, B> {
}
}
impl<A: PyObjectWrap, B: PyObjectWrap> PyObjectWrap for Either<A, B> {
impl<A: Into<PyObjectRef>, B: Into<PyObjectRef>> From<Either<A, B>> for PyObjectRef {
#[inline(always)]
fn into_object(self) -> PyObjectRef {
match self {
Either::A(a) => a.into_object(),
Either::B(b) => b.into_object(),
fn from(value: Either<A, B>) -> Self {
match value {
Either::A(a) => a.into(),
Either::B(b) => b.into(),
}
}
}

View File

@@ -10,7 +10,7 @@ use crate::{
convert::ToPyObject,
scope::Scope,
vm::VirtualMachine,
AsObject, PyObject, PyObjectRef, PyObjectWrap, PyRef, PyValue,
AsObject, PyObject, PyObjectRef, PyRef, PyValue,
};
/// Collection of object creation helpers
@@ -44,7 +44,7 @@ impl VirtualMachine {
.unwrap_or_else(|| self.ctx.none()),
self,
);
module.into_object()
module.into()
}
pub fn new_scope_with_builtins(&self) -> Scope {

View File

@@ -112,7 +112,7 @@ mod _browser {
JsFuture::from(response_format.get_response(&response)?).await
};
Ok(PyPromise::from_future(future).into_object(vm))
Ok(PyPromise::from_future(future).into_pyobject(vm))
}
#[pyfunction]
@@ -253,7 +253,7 @@ mod _browser {
})
};
Ok(PyPromise::from_future(future).into_object(vm))
Ok(PyPromise::from_future(future).into_pyobject(vm))
}
}

View File

@@ -14,12 +14,11 @@ mod _js {
use js_sys::{Array, Object, Promise, Reflect};
use rustpython_vm::{
builtins::{PyBaseExceptionRef, PyFloat, PyStrRef, PyTypeRef},
convert::ToPyObject,
convert::{IntoObject, ToPyObject},
function::{ArgCallable, OptionalArg, OptionalOption, PosArgs},
protocol::PyIterReturn,
types::{IterNext, IterNextIterable},
PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult, PyValue, TryFromObject,
VirtualMachine,
PyObjectRef, PyObjectView, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine,
};
use std::{cell, fmt, future};
use wasm_bindgen::{closure::Closure, prelude::*, JsCast};
@@ -322,11 +321,11 @@ mod _js {
}
};
stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| {
let mut pyargs = vec![PyJsValue::new(this).into_object(vm)];
let mut pyargs = vec![PyJsValue::new(this).into_pyobject(vm)];
pyargs.extend(
Vec::from(args)
.into_iter()
.map(|arg| PyJsValue::new(arg).into_object(vm)),
.map(|arg| PyJsValue::new(arg).into_pyobject(vm)),
);
let res = vm.invoke(&py_obj, pyargs);
convert::pyresult_to_jsresult(vm, res)
@@ -524,8 +523,8 @@ mod _js {
vm.invoke(
then,
(
on_fulfill.map(|c| c.into_object()),
on_reject.map(|c| c.into_object()),
on_fulfill.map(IntoObject::into_object),
on_reject.map(IntoObject::into_object),
),
),
vm,