added try downcast methods to PyDowncastFrom trait

This commit is contained in:
Nikolay Kim 2017-07-19 13:01:59 -07:00
parent 9cecfc0ec9
commit f344c4ce3d
13 changed files with 137 additions and 63 deletions

View file

@ -215,28 +215,46 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
impl _pyo3::PyDowncastFrom for #cls
{
fn downcast_from(ob: &_pyo3::PyObjectRef) -> Result<&#cls, _pyo3::PyDowncastError>
fn try_downcast_from(ob: &_pyo3::PyObjectRef) -> Option<&#cls>
{
unsafe {
let ptr = ob.as_ptr();
let checked = ffi::PyObject_TypeCheck(
ob.as_ptr(), <#cls as _pyo3::typeob::PyTypeInfo>::type_object()) != 0;
ptr, <#cls as _pyo3::typeob::PyTypeInfo>::type_object()) != 0;
if checked {
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
let ptr = (ob.as_ptr() as *mut u8).offset(offset) as *mut #cls;
Ok(ptr.as_ref().unwrap())
let ptr = (ptr as *mut u8).offset(offset) as *mut #cls;
Some(ptr.as_ref().unwrap())
} else {
Err(_pyo3::PyDowncastError(ob.py(), None))
None
}
}
}
fn try_exact_downcast_from(ob: &_pyo3::PyObjectRef) -> Option<&#cls>
{
unsafe {
let ptr = ob.as_ptr();
if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object()
{
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
let ptr = (ptr as *mut u8).offset(offset) as *mut #cls;
Some(ptr.as_ref().unwrap())
} else {
None
}
}
}
#[inline]
unsafe fn unchecked_downcast_from(ob: &_pyo3::PyObjectRef) -> &Self
{
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
let ptr = (ob.as_ptr() as *mut u8).offset(offset) as *mut #cls;
&*ptr
}
#[inline]
unsafe fn unchecked_mut_downcast_from(ob: &_pyo3::PyObjectRef) -> &mut Self
{
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
@ -246,19 +264,33 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
}
impl _pyo3::PyMutDowncastFrom for #cls
{
fn downcast_mut_from(ob: &mut _pyo3::PyObjectRef)
-> Result<&mut #cls, _pyo3::PyDowncastError>
fn try_mut_downcast_from(ob: &mut _pyo3::PyObjectRef) -> Option<&mut #cls>
{
unsafe {
let ptr = ob.as_ptr();
let checked = ffi::PyObject_TypeCheck(
ob.as_ptr(), <#cls as _pyo3::typeob::PyTypeInfo>::type_object()) != 0;
ptr, <#cls as _pyo3::typeob::PyTypeInfo>::type_object()) != 0;
if checked {
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
let ptr = (ob.as_ptr() as *mut u8).offset(offset) as *mut #cls;
Ok(ptr.as_mut().unwrap())
let ptr = (ptr as *mut u8).offset(offset) as *mut #cls;
Some(ptr.as_mut().unwrap())
} else {
Err(_pyo3::PyDowncastError(ob.py(), None))
None
}
}
}
fn try_mut_exact_downcast_from(ob: &mut _pyo3::PyObjectRef) -> Option<&mut #cls>
{
unsafe {
let ptr = ob.as_ptr();
if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object()
{
let offset = <#cls as _pyo3::typeob::PyTypeInfo>::offset();
let ptr = (ptr as *mut u8).offset(offset) as *mut #cls;
Some(ptr.as_mut().unwrap())
} else {
None
}
}
}

View file

@ -5,7 +5,8 @@
//! Python argument parsing
use ffi;
use python::Python;
use instance::AsPyRef;
use python::{Python, PyDowncastFrom};
use objects::{PyObjectRef, PyTuple, PyDict, PyString, exc};
use err::{self, PyResult};
@ -88,7 +89,7 @@ pub fn parse_args<'p>(py: Python<'p>,
if !accept_kwargs && used_keywords != nkeywords {
// check for extraneous keyword arguments
for (key, _value) in kwargs.unwrap().items() {
let key = try!(try!(key.cast_as::<PyString>(py)).to_string());
let key = PyString::downcast_from(key.as_ref(py))?.to_string()?;
if !params.iter().any(|p| p.name == key) {
return Err(err::PyErr::new::<exc::TypeError, _>(
py,

View file

@ -6,7 +6,6 @@ use err::PyResult;
use python::{Python, ToPyPointer, PyDowncastFrom};
use object::PyObject;
use objects::{PyObjectRef, PyTuple};
use objectprotocol::ObjectProtocol;
use typeob::PyTypeInfo;
use instance::Py;
@ -154,7 +153,7 @@ impl<'a, T> FromPyObject<'a> for &'a T
#[inline]
default fn extract(ob: &'a PyObjectRef) -> PyResult<&'a T>
{
Ok(ob.cast_as()?)
Ok(PyDowncastFrom::downcast_from(ob)?)
}
}

View file

@ -4,7 +4,7 @@ use std;
use ffi;
use pythonrun;
use err::{PyErr, PyResult, PyDowncastError};
use err::{PyErr, PyResult};
use instance::{AsPyRef, PyObjectWithToken};
use objects::{PyObjectRef, PyDict};
use conversion::{ToPyObject, IntoPyObject, IntoPyTuple, FromPyObject};
@ -150,12 +150,11 @@ impl PyObject {
}
/// Casts the PyObject to a concrete Python object type.
/// Fails with `PyDowncastError` if the object is not of the expected type.
#[inline]
pub fn cast_as<D>(&self, py: Python) -> Result<&D, PyDowncastError>
pub fn cast_as<D>(&self, py: Python) -> Option<&D>
where D: ::PyDowncastFrom
{
<D as ::PyDowncastFrom>::downcast_from(self.as_ref(py))
<D as ::PyDowncastFrom>::try_downcast_from(self.as_ref(py))
}
/// Extracts some type from the Python object.
@ -166,21 +165,6 @@ impl PyObject {
FromPyObject::extract(self.as_ref(py))
}
/// Calls the object.
/// This is equivalent to the Python expression: 'self(*args, **kwargs)'
#[inline]
pub fn call<A>(&self, py: Python, args: A, kwargs: Option<&PyDict>) -> PyResult<PyObject>
where A: IntoPyTuple
{
let t = args.into_tuple(py);
let result = unsafe {
PyObject::from_borrowed_ptr_or_err(
py, ffi::PyObject_Call(self.as_ptr(), t.as_ptr(), kwargs.as_ptr()))
};
py.release(t);
result
}
/// Retrieves an attribute value.
/// This is equivalent to the Python expression 'self.attr_name'.
#[inline]
@ -193,6 +177,21 @@ impl PyObject {
})
}
/// Calls the object.
/// This is equivalent to the Python expression: 'self(*args, **kwargs)'
#[inline]
pub fn call<A>(&self, py: Python, args: A, kwargs: Option<&PyDict>) -> PyResult<PyObject>
where A: IntoPyTuple
{
let t = args.into_tuple(py);
let result = unsafe {
PyObject::from_owned_ptr_or_err(
py, ffi::PyObject_Call(self.as_ptr(), t.as_ptr(), kwargs.as_ptr()))
};
py.release(t);
result
}
/// Calls a method on the object.
/// This is equivalent to the Python expression: 'self.name(*args, **kwargs)'
#[inline]

View file

@ -5,7 +5,7 @@ use std::cmp::Ordering;
use std::os::raw::c_int;
use ffi;
use err::{PyErr, PyResult, PyDowncastError, self};
use err::{self, PyErr, PyResult};
use python::{Python, ToPyPointer, PyDowncastFrom};
use object::PyObject;
use objects::{PyObjectRef, PyDict, PyString, PyIterator, PyType};
@ -122,10 +122,9 @@ pub trait ObjectProtocol {
fn get_type(&self) -> &PyType;
/// Casts the PyObject to a concrete Python object type.
/// Fails with `PyDowncastError` if the object is not of the expected type.
fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'a>>
fn cast_as<'a, D>(&'a self) -> Option<&'a D>
where D: PyDowncastFrom,
&'a PyObjectRef: std::convert::From<&'a Self>;
&'a PyObjectRef: std::convert::From<&'a Self>;
/// Extracts some type from the Python object.
/// This is a wrapper function around `FromPyObject::extract()`.
@ -353,11 +352,11 @@ impl<T> ObjectProtocol for T where T: PyObjectWithToken + ToPyPointer {
}
#[inline]
fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'a>>
fn cast_as<'a, D>(&'a self) -> Option<&'a D>
where D: PyDowncastFrom,
&'a PyObjectRef: std::convert::From<&'a Self>
{
<D as PyDowncastFrom>::downcast_from(self.into())
<D as PyDowncastFrom>::try_downcast_from(self.into())
}
#[inline]

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use ffi;
use object::PyObject;
use python::{ToPyPointer, Python};
use python::{ToPyPointer, Python, PyDowncastFrom};
use conversion::{ToPyObject, IntoPyObject};
/// Represents a Python `bool`.
@ -57,7 +57,7 @@ impl IntoPyObject for bool {
///
/// Fails with `TypeError` if the input is not a Python `bool`.
pyobject_extract!(py, obj to bool => {
Ok(try!(obj.cast_as::<PyBool>()).is_true())
Ok(PyBool::downcast_from(obj)?.is_true())
});

View file

@ -33,16 +33,14 @@ macro_rules! pyobject_downcast(
($name: ident, $checkfunction: ident) => (
impl $crate::python::PyDowncastFrom for $name
{
fn downcast_from(ob: &$crate::PyObjectRef)
-> Result<&$name, $crate::PyDowncastError>
fn try_downcast_from(ob: &$crate::PyObjectRef) -> Option<&$name>
{
use $crate::{ToPyPointer, PyObjectWithToken};
use $crate::ToPyPointer;
unsafe {
if $crate::ffi::$checkfunction(ob.as_ptr()) > 0 {
Ok($crate::std::mem::transmute(ob))
Some($crate::std::mem::transmute(ob))
} else {
Err($crate::PyDowncastError(ob.py(), None))
None
}
}
}

View file

@ -259,7 +259,7 @@ mod test {
let gil = Python::acquire_gil();
let py = gil.python();
let v = 42i32;
assert!(v.to_object(py).cast_as::<PySequence>(py).is_err());
assert!(PySequence::downcast_from(v.to_object(py).as_ref(py)).is_err());
}
#[test]
@ -267,7 +267,7 @@ mod test {
let gil = Python::acquire_gil();
let py = gil.python();
let v = "London Calling";
assert!(v.to_object(py).cast_as::<PySequence>(py).is_ok());
assert!(PySequence::downcast_from(v.to_object(py).as_ref(py)).is_ok());
}
#[test]
fn test_seq_empty() {

View file

@ -5,7 +5,7 @@ use err::PyResult;
use object::PyObject;
use objects::{PyObjectRef, PyString};
use objectprotocol::ObjectProtocol;
use python::Python;
use python::{Python, PyDowncastFrom};
use conversion::{ToPyObject, IntoPyObject, RefFromPyObject};
/// Converts Rust `str` to Python object.
@ -59,15 +59,14 @@ impl<'source> ::FromPyObject<'source> for Cow<'source, str>
{
fn extract(ob: &'source PyObjectRef) -> PyResult<Self>
{
try!(ob.cast_as::<PyString>()).to_string()
PyString::downcast_from(ob)?.to_string()
}
}
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
pyobject_extract!(py, obj to String => {
let s = try!(obj.cast_as::<PyString>());
s.to_string().map(Cow::into_owned)
PyString::downcast_from(obj)?.to_string().map(Cow::into_owned)
});
impl RefFromPyObject for str {

View file

@ -9,8 +9,7 @@ use err::{PyErr, PyResult};
use instance::{Py, PyObjectWithToken};
use object::PyObject;
use objects::PyObjectRef;
use objectprotocol::ObjectProtocol;
use python::{Python, ToPyPointer, IntoPyPointer};
use python::{Python, ToPyPointer, IntoPyPointer, PyDowncastFrom};
use conversion::{FromPyObject, ToPyObject, IntoPyTuple, IntoPyObject};
use super::exc;
@ -146,7 +145,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+
impl<'s, $($T: FromPyObject<'s>),+> FromPyObject<'s> for ($($T,)+) {
fn extract(obj: &'s PyObjectRef) -> PyResult<Self>
{
let t = try!(obj.cast_as::<PyTuple>());
let t = PyTuple::downcast_from(obj)?;
let slice = t.as_slice();
if t.len() == $length {
Ok((
@ -224,7 +223,7 @@ impl IntoPyTuple for () {
/// Returns `Ok(NoArgs)` if the input is an empty Python tuple.
/// Otherwise, returns an error.
pyobject_extract!(py, obj to NoArgs => {
let t = try!(obj.cast_as::<PyTuple>());
let t = PyTuple::downcast_from(obj)?;
if t.len() == 0 {
Ok(NoArgs)
} else {

View file

@ -9,7 +9,7 @@ use std::os::raw::c_int;
use ffi;
use typeob::{PyTypeInfo, PyTypeObject, PyObjectAlloc};
use instance::{Py, PyToken};
use instance::{Py, PyToken, PyObjectWithToken};
use object::PyObject;
use objects::{PyObjectRef, PyType, PyDict, PyModule};
use err::{PyErr, PyResult, PyDowncastError, ToPyErr};
@ -34,7 +34,30 @@ pub struct Python<'p>(PhantomData<&'p GILGuard>);
pub trait PyDowncastFrom : Sized {
/// Cast from PyObject to a concrete Python object type.
fn downcast_from(&PyObjectRef) -> Result<&Self, PyDowncastError>;
fn try_downcast_from(&PyObjectRef) -> Option<&Self>;
/// Cast from PyObject to a concrete Python object type.
fn try_exact_downcast_from(ob: &PyObjectRef) -> Option<&Self> {
Self::try_downcast_from(ob)
}
/// Cast from PyObject to a concrete Python object type.
fn downcast_from(ob: &PyObjectRef) -> Result<&Self, PyDowncastError> {
if let Some(ob) = Self::try_downcast_from(ob) {
Ok(ob)
} else {
Err(PyDowncastError(ob.py(), None))
}
}
/// Cast from PyObject to a concrete Python object type.
fn exact_downcast_from(ob: &PyObjectRef) -> Result<&Self, PyDowncastError> {
if let Some(ob) = Self::try_exact_downcast_from(ob) {
Ok(ob)
} else {
Err(PyDowncastError(ob.py(), None))
}
}
/// Cast from PyObject to a concrete Python object type.
unsafe fn unchecked_downcast_from(&PyObjectRef) -> &Self;
@ -47,7 +70,32 @@ pub trait PyDowncastFrom : Sized {
pub trait PyMutDowncastFrom : Sized {
/// Cast from PyObject to a concrete Python object type.
fn downcast_mut_from(&mut PyObjectRef) -> Result<&mut Self, PyDowncastError>;
fn try_mut_downcast_from(&mut PyObjectRef) -> Option<&mut Self>;
/// Cast from PyObject to a concrete Python object type.
fn try_mut_exact_downcast_from(ob: &mut PyObjectRef) -> Option<&mut Self> {
Self::try_mut_downcast_from(ob)
}
/// Cast from PyObject to a concrete Python object type.
fn mut_downcast_from(ob: &mut PyObjectRef) -> Result<&mut Self, PyDowncastError> {
if let Some(o) = Self::try_mut_downcast_from(ob) {
return Ok(o)
} else {
let py = unsafe { Python::assume_gil_acquired() };
Err(PyDowncastError(py, None))
}
}
/// Cast from PyObject to a concrete Python object type.
fn mut_exact_downcast_from(ob: &mut PyObjectRef) -> Result<&mut Self, PyDowncastError> {
if let Some(ob) = Self::try_mut_exact_downcast_from(ob) {
Ok(ob)
} else {
let py = unsafe { Python::assume_gil_acquired() };
Err(PyDowncastError(py, None))
}
}
}
/// Trait implemented by Python object types that allow a checked downcast.

View file

@ -111,7 +111,7 @@ fn empty_class_with_new() {
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<EmptyClassWithNew>();
assert!(typeobj.call(NoArgs, None).unwrap().cast_as::<EmptyClassWithNew>().is_ok());
assert!(typeobj.call(NoArgs, None).unwrap().cast_as::<EmptyClassWithNew>().is_some());
}
#[py::class]

View file

@ -29,7 +29,7 @@ struct Test {
impl<'p> PyMappingProtocol<'p> for Test
{
fn __getitem__(&self, idx: &PyObjectRef) -> PyResult<PyObject> {
if let Ok(slice) = idx.cast_as::<PySlice>() {
if let Some(slice) = idx.cast_as::<PySlice>() {
let indices = slice.indices(1000)?;
if indices.start == 100 && indices.stop == 200 && indices.step == 1 {
return Ok("slice".into_object(self.py()))