From c2b32703e3da8cf60b7edfeb7efd3136fba84e96 Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Mon, 1 Dec 2025 16:27:50 +0100 Subject: [PATCH 01/10] WIP --- src/buffer.rs | 4 +- src/conversion.rs | 34 ++--- src/conversions/chrono.rs | 46 +++--- src/conversions/chrono_tz.rs | 6 +- src/conversions/smallvec.rs | 4 +- src/conversions/std/array.rs | 8 +- src/conversions/std/cell.rs | 8 +- src/conversions/std/cstring.rs | 18 +-- src/conversions/std/ipaddr.rs | 23 +-- src/conversions/std/map.rs | 26 ++-- src/conversions/std/num.rs | 45 +++--- src/conversions/std/option.rs | 10 +- src/conversions/std/osstr.rs | 18 +-- src/conversions/std/path.rs | 24 +-- src/conversions/std/set.rs | 16 +- src/conversions/std/slice.rs | 11 +- src/conversions/std/string.rs | 26 ++-- src/conversions/std/time.rs | 14 +- src/conversions/std/vec.rs | 8 +- src/err/mod.rs | 6 +- src/impl_/extract_argument.rs | 17 ++- src/impl_/introspection.rs | 8 +- src/inspect/mod.rs | 259 +++++++++++++++++---------------- src/instance.rs | 6 +- src/pybacked.rs | 14 +- src/pycell.rs | 10 +- src/pyclass/guard.rs | 14 +- src/type_object.rs | 8 +- src/types/boolobject.rs | 8 +- src/types/ellipsis.rs | 4 +- src/types/float.rs | 14 +- src/types/mapping.rs | 4 +- src/types/mod.rs | 2 +- src/types/none.rs | 4 +- src/types/notimplemented.rs | 4 +- src/types/sequence.rs | 4 +- src/types/slice.rs | 6 +- src/types/tuple.rs | 6 +- src/types/weakref/anyref.rs | 10 +- src/types/weakref/proxy.rs | 10 +- 40 files changed, 392 insertions(+), 375 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index fce3f610f0a..04a7f640bc2 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -19,7 +19,7 @@ //! `PyBuffer` implementation #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python}; use crate::{Borrowed, Bound, PyErr}; use std::ffi::{ @@ -202,7 +202,7 @@ impl FromPyObject<'_, '_> for PyBuffer { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::module_attr("collections.abc", "Buffer"); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Buffer"); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { Self::get(&obj) diff --git a/src/conversion.rs b/src/conversion.rs index 5579a50c347..15e6f176714 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -4,7 +4,7 @@ use crate::impl_::pyclass::ExtractPyClassWithClone; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::pyclass::boolean_struct::False; use crate::pyclass::{PyClassGuardError, PyClassGuardMutError}; use crate::types::PyTuple; @@ -61,7 +61,7 @@ pub trait IntoPyObject<'py>: Sized { /// For most types, the return value for this method will be identical to that of [`FromPyObject::INPUT_TYPE`]. /// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument. #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::module_attr("typing", "Any"); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); /// Performs the conversion. fn into_pyobject(self, py: Python<'py>) -> Result; @@ -116,8 +116,8 @@ pub trait IntoPyObject<'py>: Sized { /// The output type of [`IntoPyObject::owned_sequence_into_pyobject`] and [`IntoPyObject::borrowed_sequence_into_pyobject`] #[cfg(feature = "experimental-inspect")] #[doc(hidden)] - const SEQUENCE_OUTPUT_TYPE: TypeHint = - TypeHint::subscript(&PyList::TYPE_HINT, &[Self::OUTPUT_TYPE]); + const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyList::TYPE_HINT, &[Self::OUTPUT_TYPE]); } pub(crate) mod private { @@ -138,7 +138,7 @@ impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self) @@ -151,7 +151,7 @@ impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.as_borrowed()) @@ -164,7 +164,7 @@ impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, 'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self) @@ -177,7 +177,7 @@ impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, 'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(*self) @@ -190,7 +190,7 @@ impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Py { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.into_bound(py)) @@ -203,7 +203,7 @@ impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Py { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.bind_borrowed(py)) @@ -219,7 +219,7 @@ where type Error = <&'a T as IntoPyObject<'py>>::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&'a T as IntoPyObject<'py>>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&'a T as IntoPyObject<'py>>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -416,7 +416,7 @@ pub trait FromPyObject<'a, 'py>: Sized { /// For example, `Vec` would be `collections.abc.Sequence[int]`. /// The default value is `typing.Any`, which is correct for any type. #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::module_attr("typing", "Any"); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); /// Extracts `Self` from the bound smart pointer `obj`. /// @@ -466,8 +466,8 @@ pub trait FromPyObject<'a, 'py>: Sized { /// The union of Sequence[Self::INPUT_TYPE] and the input sequence extraction function [`FromPyObject::sequence_extractor`] if it's defined #[cfg(feature = "experimental-inspect")] #[doc(hidden)] - const SEQUENCE_INPUT_TYPE: TypeHint = - TypeHint::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]); + const SEQUENCE_INPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]); /// Helper used to make a specialized path in extracting `DateTime` where `Tz` is /// `chrono::Local`, which will accept "naive" datetime objects as being in the local timezone. @@ -555,7 +555,7 @@ where type Error = PyClassGuardError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = ::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { Ok(obj.extract::>()?.clone()) @@ -569,7 +569,7 @@ where type Error = PyClassGuardError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = ::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { obj.cast::() @@ -586,7 +586,7 @@ where type Error = PyClassGuardMutError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = ::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { obj.cast::() diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 4e2eff64210..aec76c101db 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -44,7 +44,7 @@ use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError}; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::intern; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; @@ -75,7 +75,7 @@ impl<'py> IntoPyObject<'py> for Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDelta::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { // Total number of days @@ -110,7 +110,7 @@ impl<'py> IntoPyObject<'py> for &Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Duration::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Duration::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -122,7 +122,7 @@ impl FromPyObject<'_, '_> for Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDelta::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let delta = ob.cast::()?; @@ -161,7 +161,7 @@ impl<'py> IntoPyObject<'py> for NaiveDate { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDate::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDate::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let DateArgs { year, month, day } = (&self).into(); @@ -175,7 +175,7 @@ impl<'py> IntoPyObject<'py> for &NaiveDate { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = NaiveDate::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = NaiveDate::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -187,7 +187,7 @@ impl FromPyObject<'_, '_> for NaiveDate { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDate::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDate::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let date = &*ob.cast::()?; @@ -201,7 +201,7 @@ impl<'py> IntoPyObject<'py> for NaiveTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyTime::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let TimeArgs { @@ -228,7 +228,7 @@ impl<'py> IntoPyObject<'py> for &NaiveTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = NaiveTime::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = NaiveTime::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -240,7 +240,7 @@ impl FromPyObject<'_, '_> for NaiveTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyTime::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyTime::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let time = &*ob.cast::()?; @@ -254,7 +254,7 @@ impl<'py> IntoPyObject<'py> for NaiveDateTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let DateArgs { year, month, day } = (&self.date()).into(); @@ -282,7 +282,7 @@ impl<'py> IntoPyObject<'py> for &NaiveDateTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = NaiveDateTime::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = NaiveDateTime::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -294,7 +294,7 @@ impl FromPyObject<'_, '_> for NaiveDateTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn extract(dt: Borrowed<'_, '_, PyAny>) -> Result { let dt = &*dt.cast::()?; @@ -321,7 +321,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&DateTime>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&DateTime>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -338,7 +338,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let tz = self.timezone().into_bound_py_any(py)?.cast_into()?; @@ -385,7 +385,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn extract(dt: Borrowed<'_, 'py, PyAny>) -> Result { let dt = &*dt.cast::()?; @@ -415,7 +415,7 @@ impl<'py> IntoPyObject<'py> for FixedOffset { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyTzInfo::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let seconds_offset = self.local_minus_utc(); @@ -430,7 +430,7 @@ impl<'py> IntoPyObject<'py> for &FixedOffset { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = FixedOffset::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = FixedOffset::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -442,7 +442,7 @@ impl FromPyObject<'_, '_> for FixedOffset { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyTzInfo::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; /// Convert python tzinfo to rust [`FixedOffset`]. /// @@ -477,7 +477,7 @@ impl<'py> IntoPyObject<'py> for Utc { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyTzInfo::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { PyTzInfo::utc(py) @@ -490,7 +490,7 @@ impl<'py> IntoPyObject<'py> for &Utc { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Utc::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Utc::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -518,7 +518,7 @@ impl<'py> IntoPyObject<'py> for Local { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyTzInfo::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { static LOCAL_TZ: PyOnceLock> = PyOnceLock::new(); @@ -541,7 +541,7 @@ impl<'py> IntoPyObject<'py> for &Local { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Local::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Local::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/chrono_tz.rs b/src/conversions/chrono_tz.rs index e9c7e271ccd..1f154c9ceb1 100644 --- a/src/conversions/chrono_tz.rs +++ b/src/conversions/chrono_tz.rs @@ -37,7 +37,7 @@ use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::pybacked::PyBackedStr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; @@ -52,7 +52,7 @@ impl<'py> IntoPyObject<'py> for Tz { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyTzInfo::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { PyTzInfo::timezone(py, self.name()) @@ -65,7 +65,7 @@ impl<'py> IntoPyObject<'py> for &Tz { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Tz::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Tz::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/smallvec.rs b/src/conversions/smallvec.rs index 3c2319f3892..43618fa41e0 100644 --- a/src/conversions/smallvec.rs +++ b/src/conversions/smallvec.rs @@ -20,7 +20,7 @@ use crate::exceptions::PyTypeError; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::types::any::PyAnyMethods; use crate::types::{PySequence, PyString}; use crate::{ @@ -38,7 +38,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = A::Item::SEQUENCE_OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = A::Item::SEQUENCE_OUTPUT_TYPE; /// Turns [`SmallVec`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`] /// diff --git a/src/conversions/std/array.rs b/src/conversions/std/array.rs index 5d8034f369c..983d7fb19c7 100644 --- a/src/conversions/std/array.rs +++ b/src/conversions/std/array.rs @@ -1,6 +1,6 @@ use crate::conversion::{FromPyObjectOwned, FromPyObjectSequence, IntoPyObject}; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::types::any::PyAnyMethods; use crate::types::PySequence; use crate::{err::CastError, ffi, FromPyObject, PyAny, PyResult, PyTypeInfo, Python}; @@ -15,7 +15,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::SEQUENCE_OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::SEQUENCE_OUTPUT_TYPE; /// Turns [`[u8; N]`](std::array) into [`PyBytes`], all other `T`s will be turned into a [`PyList`] /// @@ -36,7 +36,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&[T]>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&[T]>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -51,7 +51,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::SEQUENCE_INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = T::SEQUENCE_INPUT_TYPE; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult { if let Some(extractor) = T::sequence_extractor(obj, crate::conversion::private::Token) { diff --git a/src/conversions/std/cell.rs b/src/conversions/std/cell.rs index 9a79479ad6f..b90d5432748 100644 --- a/src/conversions/std/cell.rs +++ b/src/conversions/std/cell.rs @@ -1,7 +1,7 @@ use std::cell::Cell; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{conversion::IntoPyObject, Borrowed, FromPyObject, PyAny, Python}; impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for Cell { @@ -10,7 +10,7 @@ impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for Cell { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -24,7 +24,7 @@ impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for &Cell { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -36,7 +36,7 @@ impl<'a, 'py, T: FromPyObject<'a, 'py>> FromPyObject<'a, 'py> for Cell { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = T::INPUT_TYPE; fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { ob.extract().map(Cell::new) diff --git a/src/conversions/std/cstring.rs b/src/conversions/std/cstring.rs index e0b2a22f076..2dd05265639 100644 --- a/src/conversions/std/cstring.rs +++ b/src/conversions/std/cstring.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::types::PyString; @@ -19,7 +19,7 @@ impl<'py> IntoPyObject<'py> for &CStr { type Error = Utf8Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&str>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&str>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -33,7 +33,7 @@ impl<'py> IntoPyObject<'py> for CString { type Error = Utf8Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -47,7 +47,7 @@ impl<'py> IntoPyObject<'py> for &CString { type Error = Utf8Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -61,7 +61,7 @@ impl<'py> IntoPyObject<'py> for Cow<'_, CStr> { type Error = Utf8Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) @@ -74,7 +74,7 @@ impl<'py> IntoPyObject<'py> for &Cow<'_, CStr> { type Error = Utf8Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { (&**self).into_pyobject(py) @@ -86,7 +86,7 @@ impl<'a> FromPyObject<'a, '_> for &'a CStr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result { let obj = obj.cast::()?; @@ -110,7 +110,7 @@ impl<'a> FromPyObject<'a, '_> for Cow<'a, CStr> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] @@ -128,7 +128,7 @@ impl FromPyObject<'_, '_> for CString { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] diff --git a/src/conversions/std/ipaddr.rs b/src/conversions/std/ipaddr.rs index acfab176a1a..5396edbf4f5 100644 --- a/src/conversions/std/ipaddr.rs +++ b/src/conversions/std/ipaddr.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; @@ -13,10 +13,10 @@ impl FromPyObject<'_, '_> for IpAddr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::union(&[ - TypeHint::module_attr("ipaddress", "IPv4Address"), - TypeHint::module_attr("ipaddress", "IPv6Address"), - ]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( + &PyStaticExpr::module_attr("ipaddress", "IPv4Address"), + &PyStaticExpr::module_attr("ipaddress", "IPv6Address"), + ); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { match obj.getattr(intern!(obj.py(), "packed")) { @@ -43,7 +43,7 @@ impl<'py> IntoPyObject<'py> for Ipv4Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::module_attr("ipaddress", "IPv4Address"); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("ipaddress", "IPv4Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV4_ADDRESS: PyOnceLock> = PyOnceLock::new(); @@ -59,7 +59,7 @@ impl<'py> IntoPyObject<'py> for &Ipv4Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Ipv4Addr::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Ipv4Addr::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -73,7 +73,7 @@ impl<'py> IntoPyObject<'py> for Ipv6Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::module_attr("ipaddress", "IPv6Address"); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("ipaddress", "IPv6Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV6_ADDRESS: PyOnceLock> = PyOnceLock::new(); @@ -89,7 +89,7 @@ impl<'py> IntoPyObject<'py> for &Ipv6Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Ipv6Addr::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Ipv6Addr::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -103,7 +103,8 @@ impl<'py> IntoPyObject<'py> for IpAddr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::union(&[Ipv4Addr::OUTPUT_TYPE, Ipv6Addr::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::bit_or(&Ipv4Addr::OUTPUT_TYPE, &Ipv6Addr::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { match self { @@ -119,7 +120,7 @@ impl<'py> IntoPyObject<'py> for &IpAddr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = IpAddr::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = IpAddr::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/map.rs b/src/conversions/std/map.rs index 7863530336d..c542d40450f 100644 --- a/src/conversions/std/map.rs +++ b/src/conversions/std/map.rs @@ -1,7 +1,7 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -23,8 +23,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -51,8 +51,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -78,8 +78,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -107,8 +107,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -133,8 +133,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; @@ -162,8 +162,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = - TypeHint::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 3f10b47ddac..e70e412b952 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -4,7 +4,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::py_result_ext::PyResultExt; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; @@ -30,7 +30,7 @@ macro_rules! int_fits_larger_int { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$larger_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$larger_type>::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { (self as $larger_type).into_pyobject(py) @@ -48,7 +48,7 @@ macro_rules! int_fits_larger_int { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$larger_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$larger_type>::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) @@ -64,7 +64,7 @@ macro_rules! int_fits_larger_int { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = <$larger_type>::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = <$larger_type>::INPUT_TYPE; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let val: $larger_type = obj.extract()?; @@ -113,7 +113,7 @@ macro_rules! int_convert_u64_or_i64 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { unsafe { @@ -134,7 +134,7 @@ macro_rules! int_convert_u64_or_i64 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$rust_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -145,7 +145,7 @@ macro_rules! int_convert_u64_or_i64 { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { extract_int!(obj, !0, $pylong_as_ll_or_ull, $force_index_call) @@ -167,7 +167,7 @@ macro_rules! int_fits_c_long { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { unsafe { @@ -189,7 +189,7 @@ macro_rules! int_fits_c_long { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$rust_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -206,7 +206,7 @@ macro_rules! int_fits_c_long { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; @@ -228,7 +228,7 @@ impl<'py> IntoPyObject<'py> for u8 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { unsafe { @@ -256,7 +256,7 @@ impl<'py> IntoPyObject<'py> for u8 { } #[cfg(feature = "experimental-inspect")] - const SEQUENCE_OUTPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; } impl<'py> IntoPyObject<'py> for &'_ u8 { @@ -265,7 +265,7 @@ impl<'py> IntoPyObject<'py> for &'_ u8 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = u8::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = u8::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { u8::into_pyobject(*self, py) @@ -290,14 +290,14 @@ impl<'py> IntoPyObject<'py> for &'_ u8 { } #[cfg(feature = "experimental-inspect")] - const SEQUENCE_OUTPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; } impl<'py> FromPyObject<'_, 'py> for u8 { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; @@ -324,11 +324,10 @@ impl<'py> FromPyObject<'_, 'py> for u8 { } #[cfg(feature = "experimental-inspect")] - const SEQUENCE_INPUT_TYPE: TypeHint = TypeHint::union(&[ - PyBytes::TYPE_HINT, - PyByteArray::TYPE_HINT, - TypeHint::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]), - ]); + const SEQUENCE_INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( + &PyStaticExpr::bit_or(&PyBytes::TYPE_HINT, &PyByteArray::TYPE_HINT), + &PyStaticExpr::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]), + ); } pub(crate) enum BytesSequenceExtractor<'a, 'py> { @@ -682,7 +681,7 @@ macro_rules! nonzero_int_impl { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -701,7 +700,7 @@ macro_rules! nonzero_int_impl { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$nonzero_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$nonzero_type>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -718,7 +717,7 @@ macro_rules! nonzero_int_impl { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = <$primitive_type>::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = <$primitive_type>::INPUT_TYPE; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let val: $primitive_type = obj.extract()?; diff --git a/src/conversions/std/option.rs b/src/conversions/std/option.rs index 729fcc56668..d5c70174e94 100644 --- a/src/conversions/std/option.rs +++ b/src/conversions/std/option.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ conversion::IntoPyObject, types::any::PyAnyMethods, BoundObject, FromPyObject, PyAny, Python, }; @@ -13,7 +13,8 @@ where type Output = Bound<'py, Self::Target>; type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::union(&[T::OUTPUT_TYPE, TypeHint::builtin("None")]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::bit_or(&T::OUTPUT_TYPE, &PyStaticExpr::builtin("None")); fn into_pyobject(self, py: Python<'py>) -> Result { self.map_or_else( @@ -35,7 +36,7 @@ where type Output = Bound<'py, Self::Target>; type Error = <&'a T as IntoPyObject<'py>>::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = >::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = >::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -49,7 +50,8 @@ where { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::union(&[T::INPUT_TYPE, TypeHint::builtin("None")]); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::builtin("None")); fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if obj.is_none() { diff --git a/src/conversions/std/osstr.rs b/src/conversions/std/osstr.rs index 1efd1f5161d..c9446e1b0f1 100644 --- a/src/conversions/std/osstr.rs +++ b/src/conversions/std/osstr.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::instance::Bound; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; @@ -17,7 +17,7 @@ impl<'py> IntoPyObject<'py> for &OsStr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { // If the string is UTF-8, take the quick and easy shortcut @@ -69,7 +69,7 @@ impl<'py> IntoPyObject<'py> for &&OsStr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&OsStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -81,7 +81,7 @@ impl FromPyObject<'_, '_> for OsString { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let pystring = ob.cast::()?; @@ -149,7 +149,7 @@ impl<'py> IntoPyObject<'py> for Cow<'_, OsStr> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&OsStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -163,7 +163,7 @@ impl<'py> IntoPyObject<'py> for &Cow<'_, OsStr> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&OsStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -175,7 +175,7 @@ impl<'a> FromPyObject<'a, '_> for Cow<'a, OsStr> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = OsString::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = OsString::INPUT_TYPE; fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] @@ -193,7 +193,7 @@ impl<'py> IntoPyObject<'py> for OsString { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&OsStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -207,7 +207,7 @@ impl<'py> IntoPyObject<'py> for &OsString { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&OsStr>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index dd4730cc701..7cea8a22cf1 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::{ffi, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python}; @@ -13,10 +13,10 @@ impl FromPyObject<'_, '_> for PathBuf { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::union(&[ - OsString::INPUT_TYPE, - TypeHint::module_attr("os", "PathLike"), - ]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( + &OsString::INPUT_TYPE, + &PyStaticExpr::module_attr("os", "PathLike"), + ); fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { // We use os.fspath to get the underlying path as bytes or str @@ -31,7 +31,7 @@ impl<'py> IntoPyObject<'py> for &Path { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::module_attr("pathlib", "Path"); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("pathlib", "Path"); #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -48,7 +48,7 @@ impl<'py> IntoPyObject<'py> for &&Path { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&Path>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -62,7 +62,7 @@ impl<'py> IntoPyObject<'py> for Cow<'_, Path> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&Path>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -76,7 +76,7 @@ impl<'py> IntoPyObject<'py> for &Cow<'_, Path> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&Path>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -88,7 +88,7 @@ impl<'a> FromPyObject<'a, '_> for Cow<'a, Path> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PathBuf::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = PathBuf::INPUT_TYPE; fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] @@ -106,7 +106,7 @@ impl<'py> IntoPyObject<'py> for PathBuf { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&Path>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -120,7 +120,7 @@ impl<'py> IntoPyObject<'py> for &PathBuf { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&Path>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/set.rs b/src/conversions/std/set.rs index 584be71b1c9..8254983ac63 100644 --- a/src/conversions/std/set.rs +++ b/src/conversions/std/set.rs @@ -3,7 +3,7 @@ use std::{cmp, collections, hash}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -27,7 +27,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -48,7 +48,8 @@ where type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -68,7 +69,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { @@ -104,7 +105,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -126,7 +127,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -145,7 +147,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index 8a42c138986..2ee30791210 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -21,7 +21,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&T>::SEQUENCE_OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&T>::SEQUENCE_OUTPUT_TYPE; /// Turns [`&[u8]`](std::slice) into [`PyBytes`], all other `T`s will be turned into a [`PyList`] /// @@ -45,7 +45,7 @@ impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for &'a [u8] { type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn extract(obj: crate::Borrowed<'a, 'py, PyAny>) -> Result { Ok(obj.cast::()?.as_bytes()) @@ -66,7 +66,8 @@ impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for Cow<'a, [u8]> { type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::union(&[PyBytes::TYPE_HINT, PyByteArray::TYPE_HINT]); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::bit_or(&PyBytes::TYPE_HINT, &PyByteArray::TYPE_HINT); fn extract(ob: crate::Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(bytes) = ob.cast::() { @@ -93,7 +94,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&T>::SEQUENCE_OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&T>::SEQUENCE_OUTPUT_TYPE; /// Turns `Cow<[u8]>` into [`PyBytes`], all other `T`s will be turned into a [`PyList`] /// diff --git a/src/conversions/std/string.rs b/src/conversions/std/string.rs index e6e57def1d2..4f4024ff377 100644 --- a/src/conversions/std/string.rs +++ b/src/conversions/std/string.rs @@ -1,7 +1,7 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -16,7 +16,7 @@ impl<'py> IntoPyObject<'py> for &str { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -35,7 +35,7 @@ impl<'py> IntoPyObject<'py> for &&str { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -54,7 +54,7 @@ impl<'py> IntoPyObject<'py> for Cow<'_, str> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -73,7 +73,7 @@ impl<'py> IntoPyObject<'py> for &Cow<'_, str> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -92,7 +92,7 @@ impl<'py> IntoPyObject<'py> for char { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; fn into_pyobject(self, py: Python<'py>) -> Result { let mut bytes = [0u8; 4]; @@ -111,7 +111,7 @@ impl<'py> IntoPyObject<'py> for &char { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -130,7 +130,7 @@ impl<'py> IntoPyObject<'py> for String { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PyString::new(py, &self)) @@ -148,7 +148,7 @@ impl<'py> IntoPyObject<'py> for &String { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = String::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -166,7 +166,7 @@ impl<'a> crate::conversion::FromPyObject<'a, '_> for &'a str { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result { ob.cast::()?.to_str() @@ -182,7 +182,7 @@ impl<'a> crate::conversion::FromPyObject<'a, '_> for Cow<'a, str> { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result { ob.cast::()?.to_cow() @@ -200,7 +200,7 @@ impl FromPyObject<'_, '_> for String { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { obj.cast::()?.to_cow().map(Cow::into_owned) @@ -216,7 +216,7 @@ impl FromPyObject<'_, '_> for char { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let s = obj.cast::()?.to_cow()?; diff --git a/src/conversions/std/time.rs b/src/conversions/std/time.rs index 8f36aea14bd..35fc784666b 100644 --- a/src/conversions/std/time.rs +++ b/src/conversions/std/time.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::exceptions::{PyOverflowError, PyValueError}; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(Py_LIMITED_API)] use crate::intern; use crate::sync::PyOnceLock; @@ -20,7 +20,7 @@ impl FromPyObject<'_, '_> for Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDelta::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let delta = obj.cast::()?; @@ -65,7 +65,7 @@ impl<'py> IntoPyObject<'py> for Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDelta::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let days = self.as_secs() / SECONDS_PER_DAY; @@ -88,7 +88,7 @@ impl<'py> IntoPyObject<'py> for &Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = Duration::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = Duration::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -106,7 +106,7 @@ impl FromPyObject<'_, '_> for SystemTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let duration_since_unix_epoch: Duration = obj.sub(unix_epoch_py(obj.py())?)?.extract()?; @@ -124,7 +124,7 @@ impl<'py> IntoPyObject<'py> for SystemTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyDateTime::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let duration_since_unix_epoch = @@ -142,7 +142,7 @@ impl<'py> IntoPyObject<'py> for &SystemTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = SystemTime::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = SystemTime::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/vec.rs b/src/conversions/std/vec.rs index 89806631d35..f2c65b83c45 100644 --- a/src/conversions/std/vec.rs +++ b/src/conversions/std/vec.rs @@ -1,7 +1,7 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ conversion::{FromPyObject, FromPyObjectOwned, FromPyObjectSequence, IntoPyObject}, exceptions::PyTypeError, @@ -20,7 +20,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::SEQUENCE_OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::SEQUENCE_OUTPUT_TYPE; /// Turns [`Vec`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`] /// @@ -46,7 +46,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <&[T]>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <&[T]>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -69,7 +69,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::SEQUENCE_INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = T::SEQUENCE_INPUT_TYPE; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult { if let Some(extractor) = T::sequence_extractor(obj, crate::conversion::private::Token) { diff --git a/src/err/mod.rs b/src/err/mod.rs index 9388951ec53..a4ba53ec072 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::instance::Bound; #[cfg(Py_3_11)] use crate::intern; @@ -683,7 +683,7 @@ impl<'py> IntoPyObject<'py> for PyErr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyBaseException::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyBaseException::TYPE_HINT; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -697,7 +697,7 @@ impl<'py> IntoPyObject<'py> for &PyErr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyErr::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = PyErr::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index b0aa3adf400..c7cd4ec71e6 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(any(Py_3_10, not(Py_LIMITED_API), feature = "experimental-inspect"))] use crate::types::PyString; use crate::{ @@ -61,7 +61,7 @@ pub trait PyFunctionArgument<'a, 'holder, 'py, const IMPLEMENTS_FROMPYOBJECT: bo /// Provides the type hint information for which Python types are allowed. #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint; + const INPUT_TYPE: PyStaticExpr; fn extract( obj: &'a Bound<'py, PyAny>, @@ -77,7 +77,7 @@ where type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::INPUT_TYPE; + const INPUT_TYPE: PyStaticExpr = T::INPUT_TYPE; #[inline] fn extract(obj: &'a Bound<'py, PyAny>, _: &'_ mut ()) -> Result { @@ -93,7 +93,7 @@ where type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn extract(obj: &'a Bound<'py, PyAny>, _: &'_ mut ()) -> Result { @@ -110,7 +110,8 @@ where type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = TypeHint::union(&[T::INPUT_TYPE, TypeHint::builtin("None")]); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::builtin("None")); #[inline] fn extract( @@ -131,7 +132,7 @@ impl<'a, 'holder, 'py> PyFunctionArgument<'a, 'holder, 'py, false> for &'holder type Error = as FromPyObject<'a, 'py>>::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; #[inline] fn extract( @@ -161,7 +162,7 @@ impl<'a, 'holder, T: PyClass> PyFunctionArgument<'a, 'holder, '_, false> for &'h type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn extract(obj: &'a Bound<'_, PyAny>, holder: &'holder mut Self::Holder) -> PyResult { @@ -176,7 +177,7 @@ impl<'a, 'holder, T: PyClass> PyFunctionArgument<'a, 'holder, '_ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn extract(obj: &'a Bound<'_, PyAny>, holder: &'holder mut Self::Holder) -> PyResult { diff --git a/src/impl_/introspection.rs b/src/impl_/introspection.rs index da217f92ced..06e1b1b9487 100644 --- a/src/impl_/introspection.rs +++ b/src/impl_/introspection.rs @@ -1,20 +1,20 @@ use crate::conversion::IntoPyObject; -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; /// Trait to guess a function Python return type /// /// It is useful to properly get the return type `T` when the Rust implementation returns e.g. `PyResult` pub trait PyReturnType { /// The function return type - const OUTPUT_TYPE: TypeHint; + const OUTPUT_TYPE: PyStaticExpr; } impl<'a, T: IntoPyObject<'a>> PyReturnType for T { - const OUTPUT_TYPE: TypeHint = T::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE; } impl PyReturnType for Result { - const OUTPUT_TYPE: TypeHint = T::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE; } #[repr(C)] diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index 52e6cce7bc8..a1e2830e8ed 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -3,27 +3,23 @@ //! Tracking issue: . use std::fmt; -use std::fmt::Formatter; +use std::fmt::Write; pub mod types; -/// A [type hint](https://docs.python.org/3/glossary.html#term-type-hint). +/// A Python expression. TODO: link to as /// /// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE). /// /// ``` -/// use pyo3::inspect::TypeHint; +/// use pyo3::inspect::PyStaticExpr; /// -/// const T: TypeHint = TypeHint::union(&[TypeHint::builtin("int"), TypeHint::module_attr("b", "B")]); +/// const T: PyStaticExpr = PyStaticExpr::union(&[PyStaticExpr::builtin("int"), PyStaticExpr::module_attr("b", "B")]); /// assert_eq!(T.to_string(), "int | b.B"); /// ``` #[derive(Clone, Copy)] -pub struct TypeHint { - inner: TypeHintExpr, -} - -#[derive(Clone, Copy)] -enum TypeHintExpr { +#[non_exhaustive] +pub enum PyStaticExpr { /// A local name. Used only when the module is unknown. Local { id: &'static str }, /// A built-name like `list` or `datetime`. Used for built-in types or modules. @@ -33,82 +29,94 @@ enum TypeHintExpr { module: &'static str, attr: &'static str, }, - /// A union `elts[0] | ... | elts[len]` - Union { elts: &'static [TypeHint] }, + /// A binary operator + BinOp { + left: &'static PyStaticExpr, + op: Operator, + right: &'static PyStaticExpr, + }, + /// A tuple + Tuple { elts: &'static [Self] }, /// A subscript `main[*args]` Subscript { - value: &'static TypeHint, - slice: &'static [TypeHint], + value: &'static PyStaticExpr, + slice: &'static PyStaticExpr, }, } -impl TypeHint { +impl PyStaticExpr { /// A builtin like `int` or `list` /// /// ``` - /// use pyo3::inspect::TypeHint; + /// use pyo3::inspect::PyStaticExpr; /// - /// const T: TypeHint = TypeHint::builtin("int"); + /// const T: PyStaticExpr = PyStaticExpr::builtin("int"); /// assert_eq!(T.to_string(), "int"); /// ``` pub const fn builtin(name: &'static str) -> Self { - Self { - inner: TypeHintExpr::Builtin { id: name }, - } + Self::Builtin { id: name } } /// A type contained in a module like `datetime.time` /// /// ``` - /// use pyo3::inspect::TypeHint; + /// use pyo3::inspect::PyStaticExpr; /// - /// const T: TypeHint = TypeHint::module_attr("datetime", "time"); + /// const T: PyStaticExpr = PyStaticExpr::module_attr("datetime", "time"); /// assert_eq!(T.to_string(), "datetime.time"); /// ``` pub const fn module_attr(module: &'static str, attr: &'static str) -> Self { - Self { - inner: if matches!(module.as_bytes(), b"builtins") { - TypeHintExpr::Builtin { id: attr } - } else { - TypeHintExpr::ModuleAttribute { module, attr } - }, + if matches!(module.as_bytes(), b"builtins") { + Self::Builtin { id: attr } + } else { + Self::ModuleAttribute { module, attr } } } /// A value in the local module which module is unknown #[doc(hidden)] pub const fn local(name: &'static str) -> Self { - Self { - inner: TypeHintExpr::Local { id: name }, - } + Self::Local { id: name } } - /// The union of multiple types + /// The bit or (`|`) operator, also used for type union /// /// ``` - /// use pyo3::inspect::TypeHint; + /// use pyo3::inspect::PyStaticExpr; /// - /// const T: TypeHint = TypeHint::union(&[TypeHint::builtin("int"), TypeHint::builtin("float")]); + /// const T: PyStaticExpr = PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::builtin("float")); /// assert_eq!(T.to_string(), "int | float"); /// ``` - pub const fn union(elts: &'static [TypeHint]) -> Self { - Self { - inner: TypeHintExpr::Union { elts }, + pub const fn bit_or(left: &'static Self, right: &'static Self) -> Self { + Self::BinOp { + left, + op: Operator::BitOr, + right, } } - /// A subscribed type, often a container + /// A tuple /// /// ``` - /// use pyo3::inspect::TypeHint; + /// use pyo3::inspect::PyStaticExpr; /// - /// const T: TypeHint = TypeHint::subscript(&TypeHint::builtin("dict"), &[TypeHint::builtin("int"), TypeHint::builtin("str")]); + /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("dict"), &[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")]); /// assert_eq!(T.to_string(), "dict[int, str]"); /// ``` - pub const fn subscript(value: &'static Self, slice: &'static [Self]) -> Self { - Self { - inner: TypeHintExpr::Subscript { value, slice }, - } + pub const fn tuple(elts: &'static [Self]) -> Self { + Self::Tuple { elts } + } + + /// A subscribed expression + /// + /// ``` + /// use pyo3::inspect::PyStaticExpr; + /// + /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("dict"), &PyStaticExpr::slice(&[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")])); + /// assert_eq!(T.to_string(), "dict[int, str]"); + /// ``` + pub const fn subscript(value: &'static Self, slice: &'static Self) -> Self { + Self::Subscript { value, slice } } } @@ -116,51 +124,51 @@ impl TypeHint { /// /// We use the same AST as Python: #[doc(hidden)] -pub const fn serialize_for_introspection(hint: &TypeHint, mut output: &mut [u8]) -> usize { +pub const fn serialize_for_introspection(expr: &PyStaticExpr, output: &mut [u8]) -> usize { let original_len = output.len(); - match &hint.inner { - TypeHintExpr::Local { id } => { + match expr { + PyStaticExpr::Local { id } => { output = write_slice_and_move_forward(b"{\"type\":\"local\",\"id\":\"", output); output = write_slice_and_move_forward(id.as_bytes(), output); output = write_slice_and_move_forward(b"\"}", output); } - TypeHintExpr::Builtin { id } => { + PyStaticExpr::Builtin { id } => { output = write_slice_and_move_forward(b"{\"type\":\"builtin\",\"id\":\"", output); output = write_slice_and_move_forward(id.as_bytes(), output); output = write_slice_and_move_forward(b"\"}", output); } - TypeHintExpr::ModuleAttribute { module, attr } => { + PyStaticExpr::ModuleAttribute { module, attr } => { output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"module\":\"", output); output = write_slice_and_move_forward(module.as_bytes(), output); output = write_slice_and_move_forward(b"\",\"attr\":\"", output); output = write_slice_and_move_forward(attr.as_bytes(), output); output = write_slice_and_move_forward(b"\"}", output); } - TypeHintExpr::Union { elts } => { + PyStaticExpr::BinOp { left, right, .. } => { output = write_slice_and_move_forward(b"{\"type\":\"union\",\"elts\":[", output); + output = write_expr_and_move_forward(left, output); + output = write_slice_and_move_forward(b",", output); + output = write_expr_and_move_forward(right, output); + output = write_slice_and_move_forward(b"]}", output); + } + PyStaticExpr::Tuple { elts } => { + output = write_slice_and_move_forward(b"{\"type\":\"tuple\",\"elts\":[", output); let mut i = 0; while i < elts.len() { if i > 0 { output = write_slice_and_move_forward(b",", output); } - output = write_type_hint_and_move_forward(&elts[i], output); + output = write_expr_and_move_forward(&elts[i], output); i += 1; } output = write_slice_and_move_forward(b"]}", output); } - TypeHintExpr::Subscript { value, slice } => { + PyStaticExpr::Subscript { value, slice } => { output = write_slice_and_move_forward(b"{\"type\":\"subscript\",\"value\":", output); - output = write_type_hint_and_move_forward(value, output); - output = write_slice_and_move_forward(b",\"slice\":[", output); - let mut i = 0; - while i < slice.len() { - if i > 0 { - output = write_slice_and_move_forward(b",", output); - } - output = write_type_hint_and_move_forward(&slice[i], output); - i += 1; - } - output = write_slice_and_move_forward(b"]}", output); + output = write_expr_and_move_forward(value, output); + output = write_slice_and_move_forward(b",\"slice\":", output); + output = write_expr_and_move_forward(slice, output); + output = write_slice_and_move_forward(b"}", output); } } original_len - output.len() @@ -168,65 +176,51 @@ pub const fn serialize_for_introspection(hint: &TypeHint, mut output: &mut [u8]) /// Length required by [`serialize_for_introspection`] #[doc(hidden)] -pub const fn serialized_len_for_introspection(hint: &TypeHint) -> usize { - match &hint.inner { - TypeHintExpr::Local { id } => 24 + id.len(), - TypeHintExpr::Builtin { id } => 26 + id.len(), - TypeHintExpr::ModuleAttribute { module, attr } => 42 + module.len() + attr.len(), - TypeHintExpr::Union { elts } => { - let mut count = 26; - let mut i = 0; - while i < elts.len() { - if i > 0 { - count += 1; - } - count += serialized_len_for_introspection(&elts[i]); - i += 1; - } - count - } - TypeHintExpr::Subscript { value, slice } => { - let mut count = 40 + serialized_len_for_introspection(value); - let mut i = 0; - while i < slice.len() { - if i > 0 { - count += 1; - } - count += serialized_len_for_introspection(&slice[i]); - i += 1; - } - count +pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { + match expr { + PyStaticExpr::Local { id } => 24 + id.len(), + PyStaticExpr::Builtin { id } => 26 + id.len(), + PyStaticExpr::ModuleAttribute { module, attr } => 42 + module.len() + attr.len(), + PyStaticExpr::BinOp { left, right, .. } => { + 27 + serialized_len_for_introspection(left) + serialized_len_for_introspection(right) } + PyStaticExpr::Tuple { elts } => 0, + PyStaticExpr::Subscript { value, slice } => 0, } } -impl fmt::Display for TypeHint { +impl fmt::Display for PyStaticExpr { #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match &self.inner { - TypeHintExpr::Builtin { id } | TypeHintExpr::Local { id } => id.fmt(f), - TypeHintExpr::ModuleAttribute { module, attr } => { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Builtin { id } | Self::Local { id } => id.fmt(f), + Self::ModuleAttribute { module, attr } => { module.fmt(f)?; f.write_str(".")?; attr.fmt(f) } - TypeHintExpr::Union { elts } => { - for (i, elt) in elts.iter().enumerate() { - if i > 0 { - f.write_str(" | ")?; - } - elt.fmt(f)?; - } - Ok(()) + Self::BinOp { left, op, right } => { + left.fmt(f)?; + f.write_char(' ')?; + f.write_char(match op { + Operator::BitOr => '|', + })?; + f.write_char(' ')?; + right.fmt(f) } - TypeHintExpr::Subscript { value, slice } => { + Self::Tuple { elts } => { + f.write_char('[')?; + fmt_elements(elts, f)?; + f.write_char(']') + } + Self::Subscript { value, slice } => { value.fmt(f)?; f.write_str("[")?; - for (i, elt) in slice.iter().enumerate() { - if i > 0 { - f.write_str(", ")?; - } - elt.fmt(f)?; + if let PyStaticExpr::Tuple { elts } = slice { + // We don't display the tuple parentheses + TODO + } else { + slice.fmt(f)?; } f.write_str("]") } @@ -234,6 +228,13 @@ impl fmt::Display for TypeHint { } } +/// An operator used in [`PyStaticExpr::BinaryOpt`]. +#[derive(Clone, Copy)] +#[non_exhaustive] +pub enum Operator { + BitOr, // TODO: naming +} + const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> &'a mut [u8] { // TODO: use copy_from_slice with MSRV 1.87+ let mut i = 0; @@ -244,29 +245,39 @@ const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> output.split_at_mut(value.len()).1 } -const fn write_type_hint_and_move_forward<'a>( - value: &TypeHint, +const fn write_expr_and_move_forward<'a>( + value: &PyStaticExpr, output: &'a mut [u8], ) -> &'a mut [u8] { - let written = serialize_for_introspection(value, output); + let written = serialize_expr_for_introspection(value, output); output.split_at_mut(written).1 } +fn fmt_elements(elts: &[PyStaticExpr], f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, elt) in elts.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + elt.fmt(f)?; + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; #[test] fn test_to_string() { - const T: TypeHint = TypeHint::subscript( - &TypeHint::builtin("dict"), + const T: PyStaticExpr = PyStaticExpr::subscript( + &PyStaticExpr::builtin("dict"), &[ - TypeHint::union(&[ - TypeHint::builtin("int"), - TypeHint::module_attr("builtins", "float"), - TypeHint::local("weird"), + PyStaticExpr::union(&[ + PyStaticExpr::builtin("int"), + PyStaticExpr::module_attr("builtins", "float"), + PyStaticExpr::local("weird"), ]), - TypeHint::module_attr("datetime", "time"), + PyStaticExpr::module_attr("datetime", "time"), ], ); assert_eq!(T.to_string(), "dict[int | float | weird, datetime.time]") @@ -274,11 +285,11 @@ mod tests { #[test] fn test_serialize_for_introspection() { - const T: TypeHint = TypeHint::subscript( - &TypeHint::builtin("dict"), + const T: PyStaticExpr = PyStaticExpr::subscript( + &PyStaticExpr::builtin("dict"), &[ - TypeHint::union(&[TypeHint::builtin("int"), TypeHint::local("weird")]), - TypeHint::module_attr("datetime", "time"), + PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::local("weird")), + PyStaticExpr::module_attr("datetime", "time"), ], ); const SER_LEN: usize = serialized_len_for_introspection(&T); diff --git a/src/instance.rs b/src/instance.rs index 91af1bfe555..c2765464e07 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -5,7 +5,7 @@ use crate::conversion::IntoPyObject; use crate::err::{PyErr, PyResult}; use crate::impl_::pycell::PyClassObject; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::pycell::{PyBorrowError, PyBorrowMutError}; use crate::pyclass::boolean_struct::{False, True}; use crate::types::{any::PyAnyMethods, string::PyStringMethods, typeobject::PyTypeMethods}; @@ -2249,7 +2249,7 @@ where type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; /// Extracts `Self` from the source `PyObject`. fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { @@ -2264,7 +2264,7 @@ where type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; /// Extracts `Self` from the source `PyObject`. fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { diff --git a/src/pybacked.rs b/src/pybacked.rs index d8b6db30c2a..9ff0d3e4d5c 100644 --- a/src/pybacked.rs +++ b/src/pybacked.rs @@ -1,7 +1,7 @@ //! Contains types for working with Python objects that own the underlying data. #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ types::{ bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray, @@ -97,7 +97,7 @@ impl FromPyObject<'_, '_> for PyBackedStr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let py_string = obj.cast::()?.to_owned(); @@ -111,7 +111,7 @@ impl<'py> IntoPyObject<'py> for PyBackedStr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -130,7 +130,7 @@ impl<'py> IntoPyObject<'py> for &PyBackedStr { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyString::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -246,7 +246,7 @@ impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes { type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(bytes) = obj.cast::() { @@ -276,7 +276,7 @@ impl<'py> IntoPyObject<'py> for PyBackedBytes { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { match self.storage { @@ -292,7 +292,7 @@ impl<'py> IntoPyObject<'py> for &PyBackedBytes { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyBytes::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { match &self.storage { diff --git a/src/pycell.rs b/src/pycell.rs index b0417e18cfb..ef783524d50 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -206,7 +206,7 @@ use std::ptr::NonNull; pub(crate) mod impl_; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use impl_::{PyClassBorrowChecker, PyClassObjectLayout}; /// A wrapper type for an immutably borrowed value from a [`Bound<'py, T>`]. @@ -480,7 +480,7 @@ impl<'py, T: PyClass> IntoPyObject<'py> for PyRef<'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.clone()) @@ -493,7 +493,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &'a PyRef<'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.as_borrowed()) @@ -659,7 +659,7 @@ impl<'py, T: PyClass> IntoPyObject<'py> for PyRefMut<'py, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.clone()) @@ -672,7 +672,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &'a PyRefMut<'py type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.as_borrowed()) diff --git a/src/pyclass/guard.rs b/src/pyclass/guard.rs index efb7929edb3..db1dfe3f27b 100644 --- a/src/pyclass/guard.rs +++ b/src/pyclass/guard.rs @@ -1,6 +1,6 @@ use crate::impl_::pycell::{PyClassObject, PyClassObjectLayout as _}; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::pycell::PyBorrowMutError; use crate::pycell::{impl_::PyClassBorrowChecker, PyBorrowError}; use crate::pyclass::boolean_struct::False; @@ -284,7 +284,7 @@ impl<'a, 'py, T: PyClass> FromPyObject<'a, 'py> for PyClassGuard<'a, T> { type Error = PyClassGuardError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, crate::PyAny>) -> Result { Self::try_from_class_object( @@ -302,7 +302,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for PyClassGuard<'a, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn into_pyobject(self, py: crate::Python<'py>) -> Result { @@ -316,7 +316,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &PyClassGuard<'a, T> { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn into_pyobject(self, py: crate::Python<'py>) -> Result { @@ -692,7 +692,7 @@ impl<'a, 'py, T: PyClass> FromPyObject<'a, 'py> for PyClassGuard type Error = PyClassGuardMutError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = T::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, crate::PyAny>) -> Result { Self::try_from_class_object( @@ -710,7 +710,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for PyClassGuardMut< type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn into_pyobject(self, py: crate::Python<'py>) -> Result { @@ -724,7 +724,7 @@ impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &PyClassGuardMut type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = T::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; #[inline] fn into_pyobject(self, py: crate::Python<'py>) -> Result { diff --git a/src/type_object.rs b/src/type_object.rs index e87134c7de4..8cd91a91435 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -2,7 +2,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::types::{PyAny, PyType}; use crate::{ffi, Bound, Python}; use std::ptr; @@ -58,7 +58,7 @@ pub unsafe trait PyTypeInfo: Sized { /// Provides the full python type as a type hint. #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::module_attr("typing", "Any"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; @@ -115,7 +115,7 @@ pub unsafe trait PyTypeCheck { /// Provides the full python type of the allowed values as a Python type hint. #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint; + const TYPE_HINT: PyStaticExpr; /// Checks if `object` is an instance of `Self`, which may include a subtype. /// @@ -136,7 +136,7 @@ where const NAME: &'static str = T::NAME; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = ::TYPE_HINT; + const TYPE_HINT: PyStaticExpr = ::TYPE_HINT; #[inline] fn type_check(object: &Bound<'_, PyAny>) -> bool { diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index cc33932a724..017584b877d 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -3,7 +3,7 @@ use crate::conversion::IntoPyObject; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::PyErr; @@ -147,7 +147,7 @@ impl<'py> IntoPyObject<'py> for bool { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyBool::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyBool::TYPE_HINT; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -166,7 +166,7 @@ impl<'py> IntoPyObject<'py> for &bool { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = bool::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = bool::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -186,7 +186,7 @@ impl FromPyObject<'_, '_> for bool { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyBool::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyBool::TYPE_HINT; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let err = match obj.cast::() { diff --git a/src/types/ellipsis.rs b/src/types/ellipsis.rs index 448d4d10176..864cc9759c0 100644 --- a/src/types/ellipsis.rs +++ b/src/types/ellipsis.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ ffi, ffi_ptr_ext::FfiPtrExt, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python, @@ -32,7 +32,7 @@ unsafe impl PyTypeInfo for PyEllipsis { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::module_attr("types", "EllipsisType"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("types", "EllipsisType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_Ellipsis()) } diff --git a/src/types/float.rs b/src/types/float.rs index daaebea4d66..bc82375bafd 100644 --- a/src/types/float.rs +++ b/src/types/float.rs @@ -2,7 +2,7 @@ use crate::conversion::IntoPyObject; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -79,7 +79,7 @@ impl<'py> IntoPyObject<'py> for f64 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyFloat::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -98,7 +98,7 @@ impl<'py> IntoPyObject<'py> for &f64 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = f64::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = f64::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -115,7 +115,7 @@ impl<'py> FromPyObject<'_, 'py> for f64 { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyFloat::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT; // PyFloat_AsDouble returns -1.0 upon failure #[allow(clippy::float_cmp)] @@ -152,7 +152,7 @@ impl<'py> IntoPyObject<'py> for f32 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyFloat::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -171,7 +171,7 @@ impl<'py> IntoPyObject<'py> for &f32 { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = f32::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = f32::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -188,7 +188,7 @@ impl<'a, 'py> FromPyObject<'a, 'py> for f32 { type Error = >::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyFloat::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { Ok(obj.extract::()? as f32) diff --git a/src/types/mapping.rs b/src/types/mapping.rs index d23263259ca..918c0b2dd86 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -2,7 +2,7 @@ use crate::conversion::IntoPyObject; use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::instance::Bound; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; @@ -28,7 +28,7 @@ unsafe impl PyTypeInfo for PyMapping { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::module_attr("collections.abc", "Mapping"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Mapping"); #[inline] #[allow(clippy::redundant_closure_call)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 60d10b77514..cc63097efac 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -152,7 +152,7 @@ macro_rules! pyobject_type_info_type_hint( #[macro_export] macro_rules! pyobject_type_info_type_hint( ($module:expr, $name:expr) => { - const TYPE_HINT: $crate::inspect::TypeHint = $crate::inspect::TypeHint::module_attr($module, $name); + const TYPE_HINT: $crate::inspect::PyStaticExpr = $crate::inspect::PyStaticExpr::module_attr($module, $name); }; ); diff --git a/src/types/none.rs b/src/types/none.rs index 64527734547..33104e63a95 100644 --- a/src/types/none.rs +++ b/src/types/none.rs @@ -1,6 +1,6 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ffi, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python}; /// Represents the Python `None` object. @@ -30,7 +30,7 @@ unsafe impl PyTypeInfo for PyNone { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::builtin("None"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::builtin("None"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_None()) } diff --git a/src/types/notimplemented.rs b/src/types/notimplemented.rs index 76039644592..80ea8eb3388 100644 --- a/src/types/notimplemented.rs +++ b/src/types/notimplemented.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::{ ffi, ffi_ptr_ext::FfiPtrExt, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python, @@ -32,7 +32,7 @@ unsafe impl PyTypeInfo for PyNotImplemented { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::module_attr("types", "NotImplementedType"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("types", "NotImplementedType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_NotImplemented()) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 753559d4a7f..27201f160bd 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -1,7 +1,7 @@ use crate::err::{self, PyErr, PyResult}; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::instance::Bound; use crate::internal_tricks::get_ssize_index; use crate::py_result_ext::PyResultExt; @@ -27,7 +27,7 @@ unsafe impl PyTypeInfo for PySequence { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::module_attr("collections.abc", "Sequence"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Sequence"); #[inline] fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { diff --git a/src/types/slice.rs b/src/types/slice.rs index 4203c37b890..b02c684e63f 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -2,7 +2,7 @@ use crate::err::{PyErr, PyResult}; use crate::ffi; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::types::{PyRange, PyRangeMethods}; @@ -132,7 +132,7 @@ impl<'py> IntoPyObject<'py> for PySliceIndices { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PySlice::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PySlice::new(py, self.start, self.stop, self.step)) @@ -145,7 +145,7 @@ impl<'py> IntoPyObject<'py> for &PySliceIndices { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PySlice::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PySlice::new(py, self.start, self.stop, self.step)) diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 76612b94271..2db4ac15a1b 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -614,7 +614,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: crate::inspect::TypeHint = crate::inspect::TypeHint::subscript( + const OUTPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( &PyTuple::TYPE_HINT, &[$($T::OUTPUT_TYPE ),+] ); @@ -638,7 +638,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: crate::inspect::TypeHint = crate::inspect::TypeHint::subscript( + const OUTPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( &PyTuple::TYPE_HINT, &[$(<&$T>::OUTPUT_TYPE ),+] ); @@ -914,7 +914,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: crate::inspect::TypeHint = crate::inspect::TypeHint::subscript( + const INPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( &PyTuple::TYPE_HINT, &[$($T::INPUT_TYPE ),+] ); diff --git a/src/types/weakref/anyref.rs b/src/types/weakref/anyref.rs index efb6ed4ff41..c67bbc4b199 100644 --- a/src/types/weakref/anyref.rs +++ b/src/types/weakref/anyref.rs @@ -1,7 +1,7 @@ use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::sync::PyOnceLock; use crate::type_object::{PyTypeCheck, PyTypeInfo}; use crate::types::any::PyAny; @@ -24,10 +24,10 @@ unsafe impl PyTypeCheck for PyWeakref { const NAME: &'static str = "weakref"; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::union(&[ - PyWeakrefProxy::TYPE_HINT, - ::TYPE_HINT, - ]); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::bit_or( + &PyWeakrefProxy::TYPE_HINT, + &::TYPE_HINT, + ); #[inline] fn type_check(object: &Bound<'_, PyAny>) -> bool { diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs index dce2291f3a5..d5ba1be6acf 100644 --- a/src/types/weakref/proxy.rs +++ b/src/types/weakref/proxy.rs @@ -2,7 +2,7 @@ use super::PyWeakrefMethods; use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::TypeHint; +use crate::inspect::PyStaticExpr; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; use crate::type_object::PyTypeCheck; @@ -26,10 +26,10 @@ unsafe impl PyTypeCheck for PyWeakrefProxy { const NAME: &'static str = "weakref.ProxyTypes"; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: TypeHint = TypeHint::union(&[ - TypeHint::module_attr("weakref", "ProxyType"), - TypeHint::module_attr("weakref", "CallableProxyType"), - ]); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::bit_or( + &PyStaticExpr::module_attr("weakref", "ProxyType"), + &PyStaticExpr::module_attr("weakref", "CallableProxyType"), + ); #[inline] fn type_check(object: &Bound<'_, PyAny>) -> bool { From 46d51bcca0c25d05d785add7d289d5ea43e3ba3b Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Mon, 1 Dec 2025 17:00:30 +0100 Subject: [PATCH 02/10] Draft: Move TypeHint into a Python expression AST Implement a subset of Python expr AST Adds to the "name" construct a kind to distinguish global (global name that does not need to be resolved against the current module) vs local (names that needs to be resolved). This consolidates the previous builtin/local/module construct by taking into account that modules can be relative (for example if in a #[pyclass] the user set `module=` to the submodule name) Adds also the support for `Callable[[int], float]` and a beginning of constructs to represent constants (currently only None but will be useful for typing.Literal support) TODO: - update the macro code - update the introspection code --- pyo3-macros-backend/src/frompyobject.rs | 2 +- pyo3-macros-backend/src/intopyobject.rs | 2 +- pyo3-macros-backend/src/introspection.rs | 2 +- pyo3-macros-backend/src/pyclass.rs | 15 +- pyo3-macros-backend/src/type_hint.rs | 29 +- src/buffer.rs | 3 +- src/conversion.rs | 10 +- src/conversions/std/ipaddr.rs | 10 +- src/conversions/std/map.rs | 36 ++- src/conversions/std/num.rs | 14 +- src/conversions/std/option.rs | 6 +- src/conversions/std/path.rs | 5 +- src/conversions/std/set.rs | 12 +- src/impl_/extract_argument.rs | 3 +- src/inspect/mod.rs | 342 ++++++++++++++++------- src/type_object.rs | 2 +- src/types/ellipsis.rs | 3 +- src/types/mapping.rs | 3 +- src/types/mod.rs | 7 +- src/types/none.rs | 2 +- src/types/notimplemented.rs | 3 +- src/types/sequence.rs | 3 +- src/types/tuple.rs | 14 +- src/types/weakref/proxy.rs | 4 +- 24 files changed, 360 insertions(+), 172 deletions(-) diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index b8c033cb1b3..78a8560e4f5 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -577,7 +577,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { PythonTypeHint::module_attr("_typeshed", "Incomplete") } .to_introspection_token_stream(pyo3_crate_path); - quote! { const INPUT_TYPE: #pyo3_crate_path::inspect::TypeHint = #input_type; } + quote! { const INPUT_TYPE: #pyo3_crate_path::inspect::PyStaticExpr = #input_type; } }; #[cfg(not(feature = "experimental-inspect"))] let input_type = quote! {}; diff --git a/pyo3-macros-backend/src/intopyobject.rs b/pyo3-macros-backend/src/intopyobject.rs index bd004cc6cd5..eee5d61e8b3 100644 --- a/pyo3-macros-backend/src/intopyobject.rs +++ b/pyo3-macros-backend/src/intopyobject.rs @@ -597,7 +597,7 @@ pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Resu PythonTypeHint::module_attr("_typeshed", "Incomplete") } .to_introspection_token_stream(pyo3_crate_path); - quote! { const OUTPUT_TYPE: #pyo3_path::inspect::TypeHint = #output_type; } + quote! { const OUTPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = #output_type; } }; #[cfg(not(feature = "experimental-inspect"))] let output_type = quote! {}; diff --git a/pyo3-macros-backend/src/introspection.rs b/pyo3-macros-backend/src/introspection.rs index 0aa0e68fe29..b9f992ce87c 100644 --- a/pyo3-macros-backend/src/introspection.rs +++ b/pyo3-macros-backend/src/introspection.rs @@ -401,7 +401,7 @@ impl From for IntrospectionNode<'static> { fn serialize_type_hint(hint: TokenStream, pyo3_crate_path: &PyO3CratePath) -> TokenStream { quote! {{ - const TYPE_HINT: #pyo3_crate_path::inspect::TypeHint = #hint; + const TYPE_HINT: #pyo3_crate_path::inspect::PyStaticExpr = #hint; const TYPE_HINT_LEN: usize = #pyo3_crate_path::inspect::serialized_len_for_introspection(&TYPE_HINT); const TYPE_HINT_SER: [u8; TYPE_HINT_LEN] = { let mut result: [u8; TYPE_HINT_LEN] = [0; TYPE_HINT_LEN]; diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 7380aa8eb32..435392bad38 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -443,9 +443,12 @@ fn get_class_type_hint(cls: &Ident, args: &PyClassArgs, ctx: &Ctx) -> TokenStrea let name = get_class_python_name(cls, args).to_string(); if let Some(module) = &args.options.module { let module = module.value.value(); - quote! { #pyo3_path::inspect::TypeHint::module_attr(#module, #name) } + quote! {{ + const MODULE: #pyo3_path::inspect::PyStaticExpr = #pyo3_path::inspect::PyStaticExpr::module(#module); + #pyo3_path::inspect::PyStaticExpr::attribute(&MODULE, #name) + }} } else { - quote! { #pyo3_path::inspect::TypeHint::local(#name) } + quote! { #pyo3_path::inspect::PyStaticExpr::local(#name) } } } @@ -1121,7 +1124,7 @@ fn impl_complex_enum( } }); let output_type = if cfg!(feature = "experimental-inspect") { - quote!(const OUTPUT_TYPE: #pyo3_path::inspect::TypeHint = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) + quote!(const OUTPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) } else { TokenStream::new() }; @@ -1954,7 +1957,7 @@ fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStre #[cfg(feature = "experimental-inspect")] let type_hint = { let type_hint = get_class_type_hint(cls, attr, ctx); - quote! { const TYPE_HINT: #pyo3_path::inspect::TypeHint = #type_hint; } + quote! { const TYPE_HINT: #pyo3_path::inspect::PyStaticExpr = #type_hint; } }; #[cfg(not(feature = "experimental-inspect"))] let type_hint = quote! {}; @@ -2346,7 +2349,7 @@ impl<'a> PyClassImplsBuilder<'a> { // If #cls is not extended type, we allow Self->PyObject conversion if attr.options.extends.is_none() { let output_type = if cfg!(feature = "experimental-inspect") { - quote!(const OUTPUT_TYPE: #pyo3_path::inspect::TypeHint = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) + quote!(const OUTPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) } else { TokenStream::new() }; @@ -2533,7 +2536,7 @@ impl<'a> PyClassImplsBuilder<'a> { self.attr.options.from_py_object { let input_type = if cfg!(feature = "experimental-inspect") { - quote!(const INPUT_TYPE: #pyo3_path::inspect::TypeHint = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) + quote!(const INPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) } else { TokenStream::new() }; diff --git a/pyo3-macros-backend/src/type_hint.rs b/pyo3-macros-backend/src/type_hint.rs index 1a8378e2275..2ccaa05694b 100644 --- a/pyo3-macros-backend/src/type_hint.rs +++ b/pyo3-macros-backend/src/type_hint.rs @@ -121,13 +121,16 @@ impl PythonTypeHint { pub fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { match &self.0 { PythonTypeHintVariant::Local(name) => { - quote! { #pyo3_crate_path::inspect::TypeHint::local(#name) } + quote! { #pyo3_crate_path::inspect::PyStaticExpr::local(#name) } } PythonTypeHintVariant::ModuleAttribute { module, attr } => { if module == "builtins" { - quote! { #pyo3_crate_path::inspect::TypeHint::builtin(#attr) } + quote! { #pyo3_crate_path::inspect::PyStaticExpr::builtin(#attr) } } else { - quote! { #pyo3_crate_path::inspect::TypeHint::module_attr(#module, #attr) } + quote! {{ + const MODULE: #pyo3_crate_path::inspect::PyStaticExpr = #pyo3_crate_path::inspect::PyStaticExpr::module(#module); + #pyo3_crate_path::inspect::PyStaticExpr::attribute(&MODULE, #attr) + }} } } PythonTypeHintVariant::FromPyObject(t) => { @@ -154,17 +157,23 @@ impl PythonTypeHint { quote! { <#t as #pyo3_crate_path::type_object::PyTypeCheck>::TYPE_HINT } } PythonTypeHintVariant::Union(elements) => { - let elements = elements + elements .iter() - .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)); - quote! { #pyo3_crate_path::inspect::TypeHint::union(&[#(#elements),*]) } + .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)) + .reduce(|left, right| quote! { #pyo3_crate_path::inspect::PyStaticExpr::bit_or(&#left, &#right) }) + .expect("unions must not be empty") } PythonTypeHintVariant::Subscript { value, slice } => { let value = value.to_introspection_token_stream(pyo3_crate_path); - let slice = slice - .iter() - .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)); - quote! { #pyo3_crate_path::inspect::TypeHint::subscript(&#value, &[#(#slice),*]) } + let slice = if slice.len() == 1 { + slice[0].to_introspection_token_stream(pyo3_crate_path) + } else { + let elts = slice + .iter() + .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)); + quote! { #pyo3_crate_path::inspect::PyStaticExpr::tuple(&[#(#elts),*]) } + }; + quote! { #pyo3_crate_path::inspect::PyStaticExpr::subscript(&#value, &#slice) } } } } diff --git a/src/buffer.rs b/src/buffer.rs index 04a7f640bc2..eb2625fed4b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -202,7 +202,8 @@ impl FromPyObject<'_, '_> for PyBuffer { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Buffer"); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Buffer"); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { Self::get(&obj) diff --git a/src/conversion.rs b/src/conversion.rs index 15e6f176714..c67b3ad19b2 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -61,7 +61,8 @@ pub trait IntoPyObject<'py>: Sized { /// For most types, the return value for this method will be identical to that of [`FromPyObject::INPUT_TYPE`]. /// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument. #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); /// Performs the conversion. fn into_pyobject(self, py: Python<'py>) -> Result; @@ -117,7 +118,7 @@ pub trait IntoPyObject<'py>: Sized { #[cfg(feature = "experimental-inspect")] #[doc(hidden)] const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyList::TYPE_HINT, &[Self::OUTPUT_TYPE]); + PyStaticExpr::subscript(&PyList::TYPE_HINT, &Self::OUTPUT_TYPE); } pub(crate) mod private { @@ -416,7 +417,8 @@ pub trait FromPyObject<'a, 'py>: Sized { /// For example, `Vec` would be `collections.abc.Sequence[int]`. /// The default value is `typing.Any`, which is correct for any type. #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); + const INPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); /// Extracts `Self` from the bound smart pointer `obj`. /// @@ -467,7 +469,7 @@ pub trait FromPyObject<'a, 'py>: Sized { #[cfg(feature = "experimental-inspect")] #[doc(hidden)] const SEQUENCE_INPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]); + PyStaticExpr::subscript(&PySequence::TYPE_HINT, &Self::INPUT_TYPE); /// Helper used to make a specialized path in extracting `DateTime` where `Tz` is /// `chrono::Local`, which will accept "naive" datetime objects as being in the local timezone. diff --git a/src/conversions/std/ipaddr.rs b/src/conversions/std/ipaddr.rs index 5396edbf4f5..ea2eafcc285 100644 --- a/src/conversions/std/ipaddr.rs +++ b/src/conversions/std/ipaddr.rs @@ -14,8 +14,8 @@ impl FromPyObject<'_, '_> for IpAddr { #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( - &PyStaticExpr::module_attr("ipaddress", "IPv4Address"), - &PyStaticExpr::module_attr("ipaddress", "IPv6Address"), + &PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv4Address"), + &PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv6Address"), ); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { @@ -43,7 +43,8 @@ impl<'py> IntoPyObject<'py> for Ipv4Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("ipaddress", "IPv4Address"); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv4Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV4_ADDRESS: PyOnceLock> = PyOnceLock::new(); @@ -73,7 +74,8 @@ impl<'py> IntoPyObject<'py> for Ipv6Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("ipaddress", "IPv6Address"); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv6Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV6_ADDRESS: PyOnceLock> = PyOnceLock::new(); diff --git a/src/conversions/std/map.rs b/src/conversions/std/map.rs index c542d40450f..8040abf2304 100644 --- a/src/conversions/std/map.rs +++ b/src/conversions/std/map.rs @@ -23,8 +23,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[K::OUTPUT_TYPE, V::OUTPUT_TYPE]), + ); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -51,8 +53,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]), + ); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -78,8 +82,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::OUTPUT_TYPE, V::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[K::OUTPUT_TYPE, V::OUTPUT_TYPE]), + ); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -107,8 +113,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]), + ); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -133,8 +141,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[K::INPUT_TYPE, V::INPUT_TYPE]), + ); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; @@ -162,8 +172,10 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyDict::TYPE_HINT, &[K::INPUT_TYPE, V::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( + &PyDict::TYPE_HINT, + &PyStaticExpr::tuple(&[K::INPUT_TYPE, V::INPUT_TYPE]), + ); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index e70e412b952..25c618ddbb3 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -326,7 +326,7 @@ impl<'py> FromPyObject<'_, 'py> for u8 { #[cfg(feature = "experimental-inspect")] const SEQUENCE_INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( &PyStaticExpr::bit_or(&PyBytes::TYPE_HINT, &PyByteArray::TYPE_HINT), - &PyStaticExpr::subscript(&PySequence::TYPE_HINT, &[Self::INPUT_TYPE]), + &PyStaticExpr::subscript(&PySequence::TYPE_HINT, &Self::INPUT_TYPE), ); } @@ -429,7 +429,7 @@ mod fast_128bit_int_conversion { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { #[cfg(Py_3_13)] @@ -456,7 +456,7 @@ mod fast_128bit_int_conversion { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$rust_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -473,7 +473,7 @@ mod fast_128bit_int_conversion { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let num = nb_index(&ob)?; @@ -582,7 +582,7 @@ mod slow_128bit_int_conversion { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let lower = (self as u64).into_pyobject(py)?; @@ -610,7 +610,7 @@ mod slow_128bit_int_conversion { type Error = Infallible; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: TypeHint = <$rust_type>::OUTPUT_TYPE; + const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { @@ -627,7 +627,7 @@ mod slow_128bit_int_conversion { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: TypeHint = PyInt::TYPE_HINT; + const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let py = ob.py(); diff --git a/src/conversions/std/option.rs b/src/conversions/std/option.rs index d5c70174e94..49541fd2e26 100644 --- a/src/conversions/std/option.rs +++ b/src/conversions/std/option.rs @@ -13,8 +13,7 @@ where type Output = Bound<'py, Self::Target>; type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::bit_or(&T::OUTPUT_TYPE, &PyStaticExpr::builtin("None")); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::OUTPUT_TYPE, &PyStaticExpr::none()); fn into_pyobject(self, py: Python<'py>) -> Result { self.map_or_else( @@ -50,8 +49,7 @@ where { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::builtin("None")); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::none()); fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if obj.is_none() { diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index 7cea8a22cf1..df167b78663 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -15,7 +15,7 @@ impl FromPyObject<'_, '_> for PathBuf { #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( &OsString::INPUT_TYPE, - &PyStaticExpr::module_attr("os", "PathLike"), + &PyStaticExpr::attribute(&PyStaticExpr::module("os"), "PathLike"), ); fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { @@ -31,7 +31,8 @@ impl<'py> IntoPyObject<'py> for &Path { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::module_attr("pathlib", "Path"); + const OUTPUT_TYPE: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("pathlib"), "Path"); #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/set.rs b/src/conversions/std/set.rs index 8254983ac63..4821ddb3c6c 100644 --- a/src/conversions/std/set.rs +++ b/src/conversions/std/set.rs @@ -27,7 +27,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -49,7 +49,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); + PyStaticExpr::subscript(&PySet::TYPE_HINT, &<&K>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -69,7 +69,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { @@ -105,7 +105,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::OUTPUT_TYPE]); + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -128,7 +128,7 @@ where #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySet::TYPE_HINT, &[<&K>::OUTPUT_TYPE]); + PyStaticExpr::subscript(&PySet::TYPE_HINT, &<&K>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -147,7 +147,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &[K::INPUT_TYPE]); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index c7cd4ec71e6..cf93806b27d 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -110,8 +110,7 @@ where type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::builtin("None")); + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::none()); #[inline] fn extract( diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index a1e2830e8ed..936fa5531e7 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -2,50 +2,66 @@ //! //! Tracking issue: . -use std::fmt; -use std::fmt::Write; +use std::fmt::{self, Display, Write}; pub mod types; -/// A Python expression. TODO: link to as +/// A Python expression. +/// +/// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar) /// /// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE). /// /// ``` /// use pyo3::inspect::PyStaticExpr; /// -/// const T: PyStaticExpr = PyStaticExpr::union(&[PyStaticExpr::builtin("int"), PyStaticExpr::module_attr("b", "B")]); +/// const T: PyStaticExpr = PyStaticExpr::union(&[PyStaticExpr::builtin("int"), PyStaticExpr::attribute(&PyStaticExpr::module("b"), "B")]); /// assert_eq!(T.to_string(), "int | b.B"); /// ``` #[derive(Clone, Copy)] #[non_exhaustive] +#[allow(missing_docs)] pub enum PyStaticExpr { - /// A local name. Used only when the module is unknown. - Local { id: &'static str }, - /// A built-name like `list` or `datetime`. Used for built-in types or modules. - Builtin { id: &'static str }, - /// A module member like `datetime.time` where module = `datetime` and attr = `time` - ModuleAttribute { - module: &'static str, + /// A constant like `None` or `123` + Constant { value: PyStaticConstant }, + /// A name + Name { + id: &'static str, + kind: PyStaticNameKind, + }, + /// An attribute `value.attr` + Attribute { + value: &'static Self, attr: &'static str, }, /// A binary operator BinOp { - left: &'static PyStaticExpr, - op: Operator, - right: &'static PyStaticExpr, + left: &'static Self, + op: PyStaticOperator, + right: &'static Self, }, /// A tuple Tuple { elts: &'static [Self] }, + /// A list + List { elts: &'static [Self] }, /// A subscript `main[*args]` Subscript { - value: &'static PyStaticExpr, - slice: &'static PyStaticExpr, + value: &'static Self, + slice: &'static Self, }, } impl PyStaticExpr { - /// A builtin like `int` or `list` + /// A value in the local context. Can be a locally defined class, a module relative to the current one... + #[doc(hidden)] + pub const fn local(id: &'static str) -> Self { + Self::Name { + id, + kind: PyStaticNameKind::Local, + } + } + + /// A global builtin like `int` or `list` /// /// ``` /// use pyo3::inspect::PyStaticExpr; @@ -53,30 +69,38 @@ impl PyStaticExpr { /// const T: PyStaticExpr = PyStaticExpr::builtin("int"); /// assert_eq!(T.to_string(), "int"); /// ``` - pub const fn builtin(name: &'static str) -> Self { - Self::Builtin { id: name } + pub const fn builtin(id: &'static str) -> Self { + Self::Name { + id, + kind: PyStaticNameKind::Global, + } } - /// A type contained in a module like `datetime.time` + /// An absolute module name like `datetime` or `collections.abc` /// /// ``` /// use pyo3::inspect::PyStaticExpr; /// - /// const T: PyStaticExpr = PyStaticExpr::module_attr("datetime", "time"); - /// assert_eq!(T.to_string(), "datetime.time"); + /// const T: PyStaticExpr = PyStaticExpr::module("datetime"); + /// assert_eq!(T.to_string(), "datetime"); /// ``` - pub const fn module_attr(module: &'static str, attr: &'static str) -> Self { - if matches!(module.as_bytes(), b"builtins") { - Self::Builtin { id: attr } - } else { - Self::ModuleAttribute { module, attr } + pub const fn module(id: &'static str) -> Self { + Self::Name { + id, + kind: PyStaticNameKind::Global, } } - /// A value in the local module which module is unknown - #[doc(hidden)] - pub const fn local(name: &'static str) -> Self { - Self::Local { id: name } + /// A type contained in a module like `datetime.time` + /// + /// ``` + /// use pyo3::inspect::PyStaticExpr; + /// + /// const T: PyStaticExpr = PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"); + /// assert_eq!(T.to_string(), "datetime.time"); + /// ``` + pub const fn attribute(value: &'static Self, attr: &'static str) -> Self { + Self::Attribute { value, attr } } /// The bit or (`|`) operator, also used for type union @@ -90,7 +114,7 @@ impl PyStaticExpr { pub const fn bit_or(left: &'static Self, right: &'static Self) -> Self { Self::BinOp { left, - op: Operator::BitOr, + op: PyStaticOperator::BitOr, right, } } @@ -100,68 +124,111 @@ impl PyStaticExpr { /// ``` /// use pyo3::inspect::PyStaticExpr; /// - /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("dict"), &[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")]); - /// assert_eq!(T.to_string(), "dict[int, str]"); + /// const T: PyStaticExpr = PyStaticExpr::tuple(&[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")]); + /// assert_eq!(T.to_string(), "(int, str)"); /// ``` pub const fn tuple(elts: &'static [Self]) -> Self { Self::Tuple { elts } } + /// A list + /// + /// ``` + /// use pyo3::inspect::PyStaticExpr; + /// + /// const T: PyStaticExpr = PyStaticExpr::subscript( + /// &PyStaticExpr::builtin("Callable"), + /// &PyStaticExpr::tuple(&[ + /// &PyStaticExpr::list(&[PyStaticExpr::builtin("int")]), + /// PyStaticExpr::builtin("str") + /// ]) + /// ); + /// assert_eq!(T.to_string(), "Callable[[int], str]"); + /// ``` + pub const fn list(elts: &'static [Self]) -> Self { + Self::List { elts } + } + /// A subscribed expression /// /// ``` /// use pyo3::inspect::PyStaticExpr; /// - /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("dict"), &PyStaticExpr::slice(&[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")])); - /// assert_eq!(T.to_string(), "dict[int, str]"); + /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("list"), &PyStaticExpr::builtin("int"))); + /// assert_eq!(T.to_string(), "list[int"); /// ``` pub const fn subscript(value: &'static Self, slice: &'static Self) -> Self { Self::Subscript { value, slice } } + + /// The `None` constant + /// + /// ``` + /// use pyo3::inspect::PyStaticExpr; + /// + /// const T: PyStaticExpr = PyStaticExpr::none(); + /// assert_eq!(T.to_string(), "None"); + /// ``` + #[doc(hidden)] + pub const fn none() -> Self { + Self::Constant { + value: PyStaticConstant::None, + } + } } /// Serialize the type for introspection and return the number of written bytes -/// -/// We use the same AST as Python: #[doc(hidden)] -pub const fn serialize_for_introspection(expr: &PyStaticExpr, output: &mut [u8]) -> usize { +pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [u8]) -> usize { let original_len = output.len(); match expr { - PyStaticExpr::Local { id } => { - output = write_slice_and_move_forward(b"{\"type\":\"local\",\"id\":\"", output); - output = write_slice_and_move_forward(id.as_bytes(), output); - output = write_slice_and_move_forward(b"\"}", output); - } - PyStaticExpr::Builtin { id } => { - output = write_slice_and_move_forward(b"{\"type\":\"builtin\",\"id\":\"", output); + PyStaticExpr::Constant { value } => match value { + PyStaticConstant::None => { + output = write_slice_and_move_forward( + b"{\"type\":\"constant\",\"kind\":\"none\"}", + output, + ) + } + }, + PyStaticExpr::Name { id, kind } => { + output = write_slice_and_move_forward(b"{\"type\":\"", output); + output = write_slice_and_move_forward( + match kind { + PyStaticNameKind::Local => b"local", + PyStaticNameKind::Global => b"global", + }, + output, + ); + output = write_slice_and_move_forward(b"\",\"id\":\"", output); output = write_slice_and_move_forward(id.as_bytes(), output); output = write_slice_and_move_forward(b"\"}", output); } - PyStaticExpr::ModuleAttribute { module, attr } => { - output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"module\":\"", output); - output = write_slice_and_move_forward(module.as_bytes(), output); - output = write_slice_and_move_forward(b"\",\"attr\":\"", output); + PyStaticExpr::Attribute { value, attr } => { + output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"value\":", output); + output = write_expr_and_move_forward(value, output); + output = write_slice_and_move_forward(b",\"attr\":\"", output); output = write_slice_and_move_forward(attr.as_bytes(), output); output = write_slice_and_move_forward(b"\"}", output); } - PyStaticExpr::BinOp { left, right, .. } => { - output = write_slice_and_move_forward(b"{\"type\":\"union\",\"elts\":[", output); + PyStaticExpr::BinOp { left, op, right } => { + output = write_slice_and_move_forward(b"{\"type\":\"binop\",\"left\":", output); output = write_expr_and_move_forward(left, output); - output = write_slice_and_move_forward(b",", output); + output = write_slice_and_move_forward(b",\"op\":\"", output); + output = write_slice_and_move_forward( + match op { + PyStaticOperator::BitOr => b"bitor", + }, + output, + ); + output = write_slice_and_move_forward(b"\",\"right\":", output); output = write_expr_and_move_forward(right, output); - output = write_slice_and_move_forward(b"]}", output); + output = write_slice_and_move_forward(b"}", output); } PyStaticExpr::Tuple { elts } => { - output = write_slice_and_move_forward(b"{\"type\":\"tuple\",\"elts\":[", output); - let mut i = 0; - while i < elts.len() { - if i > 0 { - output = write_slice_and_move_forward(b",", output); - } - output = write_expr_and_move_forward(&elts[i], output); - i += 1; - } - output = write_slice_and_move_forward(b"]}", output); + output = write_container_and_move_forward(b"tuple", elts, output); + } + PyStaticExpr::List { elts } => { + output = write_container_and_move_forward(b"list", elts, output); } PyStaticExpr::Subscript { value, slice } => { output = write_slice_and_move_forward(b"{\"type\":\"subscript\",\"value\":", output); @@ -178,14 +245,30 @@ pub const fn serialize_for_introspection(expr: &PyStaticExpr, output: &mut [u8]) #[doc(hidden)] pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { match expr { - PyStaticExpr::Local { id } => 24 + id.len(), - PyStaticExpr::Builtin { id } => 26 + id.len(), - PyStaticExpr::ModuleAttribute { module, attr } => 42 + module.len() + attr.len(), - PyStaticExpr::BinOp { left, right, .. } => { - 27 + serialized_len_for_introspection(left) + serialized_len_for_introspection(right) + PyStaticExpr::Constant { value } => match value { + PyStaticConstant::None => 34, + }, + PyStaticExpr::Name { id, kind } => { + (match kind { + PyStaticNameKind::Local => 24, + PyStaticNameKind::Global => 25, + }) + id.len() + } + PyStaticExpr::Attribute { value, attr } => { + 39 + serialized_len_for_introspection(value) + attr.len() + } + PyStaticExpr::BinOp { left, op, right } => { + 41 + serialized_len_for_introspection(left) + + match op { + PyStaticOperator::BitOr => 5, + } + + serialized_len_for_introspection(right) + } + PyStaticExpr::Tuple { elts } => 5 + serialized_container_len_for_introspection(elts), + PyStaticExpr::List { elts } => 4 + serialized_container_len_for_introspection(elts), + PyStaticExpr::Subscript { value, slice } => { + 39 + serialized_len_for_introspection(value) + serialized_len_for_introspection(slice) } - PyStaticExpr::Tuple { elts } => 0, - PyStaticExpr::Subscript { value, slice } => 0, } } @@ -193,22 +276,33 @@ impl fmt::Display for PyStaticExpr { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Builtin { id } | Self::Local { id } => id.fmt(f), - Self::ModuleAttribute { module, attr } => { - module.fmt(f)?; + Self::Constant { value } => match value { + PyStaticConstant::None => f.write_str("None"), + }, + Self::Name { id, .. } => f.write_str(id), + Self::Attribute { value, attr } => { + value.fmt(f)?; f.write_str(".")?; - attr.fmt(f) + f.write_str(attr) } Self::BinOp { left, op, right } => { left.fmt(f)?; f.write_char(' ')?; f.write_char(match op { - Operator::BitOr => '|', + PyStaticOperator::BitOr => '|', })?; f.write_char(' ')?; right.fmt(f) } Self::Tuple { elts } => { + f.write_char('(')?; + fmt_elements(elts, f)?; + if elts.len() == 1 { + f.write_char(',')?; + } + f.write_char(')') + } + Self::List { elts } => { f.write_char('[')?; fmt_elements(elts, f)?; f.write_char(']') @@ -218,7 +312,7 @@ impl fmt::Display for PyStaticExpr { f.write_str("[")?; if let PyStaticExpr::Tuple { elts } = slice { // We don't display the tuple parentheses - TODO + fmt_elements(elts, f)?; } else { slice.fmt(f)?; } @@ -228,11 +322,33 @@ impl fmt::Display for PyStaticExpr { } } +/// A PyO3 extension to the Python AST to know more about [`PyStaticExpr::Name`]. +#[derive(Clone, Copy)] +#[non_exhaustive] +pub enum PyStaticNameKind { + /// A local name, relative to the current module + Local, + /// A global name, can be a module like `datetime`, a builtin like `int`... + Global, +} + +/// A PyO3 extension to the Python AST to know more about [`PyStaticExpr::Constant`]. +/// +/// This enables advanced features like escaping. +#[derive(Clone, Copy)] +#[non_exhaustive] +pub enum PyStaticConstant { + /// None + None, + // TODO: add Bool(bool), String(&'static str)... (is useful for Literal["foo", "bar"] types) +} + /// An operator used in [`PyStaticExpr::BinaryOpt`]. #[derive(Clone, Copy)] #[non_exhaustive] -pub enum Operator { - BitOr, // TODO: naming +pub enum PyStaticOperator { + /// `|` operator + BitOr, } const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> &'a mut [u8] { @@ -249,16 +365,48 @@ const fn write_expr_and_move_forward<'a>( value: &PyStaticExpr, output: &'a mut [u8], ) -> &'a mut [u8] { - let written = serialize_expr_for_introspection(value, output); + let written = serialize_for_introspection(value, output); output.split_at_mut(written).1 } +const fn write_container_and_move_forward<'a>( + name: &'static [u8], + elts: &[PyStaticExpr], + mut output: &'a mut [u8], +) -> &'a mut [u8] { + output = write_slice_and_move_forward(b"{\"type\":\"", output); + output = write_slice_and_move_forward(name, output); + output = write_slice_and_move_forward(b"\",\"elts\":[", output); + let mut i = 0; + while i < elts.len() { + if i > 0 { + output = write_slice_and_move_forward(b",", output); + } + output = write_expr_and_move_forward(&elts[i], output); + i += 1; + } + write_slice_and_move_forward(b"]}", output) +} + +const fn serialized_container_len_for_introspection(elts: &[PyStaticExpr]) -> usize { + let mut len = 20; + let mut i = 0; + while i < elts.len() { + if i > 0 { + len += 1; + } + len += serialized_len_for_introspection(&elts[i]); + i += 1; + } + len +} + fn fmt_elements(elts: &[PyStaticExpr], f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, elt) in elts.iter().enumerate() { if i > 0 { f.write_str(", ")?; - elt.fmt(f)?; } + elt.fmt(f)?; } Ok(()) } @@ -271,26 +419,28 @@ mod tests { fn test_to_string() { const T: PyStaticExpr = PyStaticExpr::subscript( &PyStaticExpr::builtin("dict"), - &[ - PyStaticExpr::union(&[ - PyStaticExpr::builtin("int"), - PyStaticExpr::module_attr("builtins", "float"), - PyStaticExpr::local("weird"), - ]), - PyStaticExpr::module_attr("datetime", "time"), - ], + &PyStaticExpr::tuple(&[ + PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::local("weird")), + PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"), + ]), ); - assert_eq!(T.to_string(), "dict[int | float | weird, datetime.time]") + assert_eq!(T.to_string(), "dict[int | weird, datetime.time]") } #[test] fn test_serialize_for_introspection() { const T: PyStaticExpr = PyStaticExpr::subscript( - &PyStaticExpr::builtin("dict"), - &[ - PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::local("weird")), - PyStaticExpr::module_attr("datetime", "time"), - ], + &PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Callable"), + &PyStaticExpr::tuple(&[ + PyStaticExpr::list(&[ + PyStaticExpr::bit_or( + &PyStaticExpr::builtin("int"), + &PyStaticExpr::local("weird"), + ), + PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"), + ]), + PyStaticExpr::none(), + ]), ); const SER_LEN: usize = serialized_len_for_introspection(&T); const SER: [u8; SER_LEN] = { @@ -300,7 +450,7 @@ mod tests { }; assert_eq!( std::str::from_utf8(&SER).unwrap(), - r#"{"type":"subscript","value":{"type":"builtin","id":"dict"},"slice":[{"type":"union","elts":[{"type":"builtin","id":"int"},{"type":"local","id":"weird"}]},{"type":"attribute","module":"datetime","attr":"time"}]}"# + r#"{"type":"subscript","value":{"type":"attribute","value":{"type":"global","id":"typing"},"attr":"Callable"},"slice":{"type":"tuple","elts":[{"type":"list","elts":[{"type":"binop","left":{"type":"global","id":"int"},"op":"bitor","right":{"type":"local","id":"weird"}},{"type":"attribute","value":{"type":"global","id":"datetime"},"attr":"time"}]},{"type":"constant","kind":"none"}]}}"# ) } } diff --git a/src/type_object.rs b/src/type_object.rs index 8cd91a91435..7b1cc6e3987 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -58,7 +58,7 @@ pub unsafe trait PyTypeInfo: Sized { /// Provides the full python type as a type hint. #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("typing", "Any"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; diff --git a/src/types/ellipsis.rs b/src/types/ellipsis.rs index 864cc9759c0..94019064c4c 100644 --- a/src/types/ellipsis.rs +++ b/src/types/ellipsis.rs @@ -32,7 +32,8 @@ unsafe impl PyTypeInfo for PyEllipsis { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("types", "EllipsisType"); + const TYPE_HINT: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("types"), "EllipsisType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_Ellipsis()) } diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 918c0b2dd86..467433e30ed 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -28,7 +28,8 @@ unsafe impl PyTypeInfo for PyMapping { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Mapping"); + const TYPE_HINT: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Mapping"); #[inline] #[allow(clippy::redundant_closure_call)] diff --git a/src/types/mod.rs b/src/types/mod.rs index cc63097efac..3bec2a4bdd6 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -152,7 +152,12 @@ macro_rules! pyobject_type_info_type_hint( #[macro_export] macro_rules! pyobject_type_info_type_hint( ($module:expr, $name:expr) => { - const TYPE_HINT: $crate::inspect::PyStaticExpr = $crate::inspect::PyStaticExpr::module_attr($module, $name); + const TYPE_HINT: $crate::inspect::PyStaticExpr = if ::std::matches!($module.as_bytes(), b"builtins") { + $crate::inspect::PyStaticExpr::builtin($name) + } else { + const MODULE: $crate::inspect::PyStaticExpr = $crate::inspect::PyStaticExpr::module($module); + $crate::inspect::PyStaticExpr::attribute(&MODULE, $name) + }; }; ); diff --git a/src/types/none.rs b/src/types/none.rs index 33104e63a95..4e664de1c51 100644 --- a/src/types/none.rs +++ b/src/types/none.rs @@ -30,7 +30,7 @@ unsafe impl PyTypeInfo for PyNone { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::builtin("None"); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::none(); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_None()) } diff --git a/src/types/notimplemented.rs b/src/types/notimplemented.rs index 80ea8eb3388..1d7a6c980d3 100644 --- a/src/types/notimplemented.rs +++ b/src/types/notimplemented.rs @@ -32,7 +32,8 @@ unsafe impl PyTypeInfo for PyNotImplemented { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("types", "NotImplementedType"); + const TYPE_HINT: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("types"), "NotImplementedType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_NotImplemented()) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 27201f160bd..4accd0c0c25 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -27,7 +27,8 @@ unsafe impl PyTypeInfo for PySequence { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::module_attr("collections.abc", "Sequence"); + const TYPE_HINT: PyStaticExpr = + PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Sequence"); #[inline] fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 2db4ac15a1b..b87cafc5fc4 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -2,6 +2,8 @@ use crate::ffi::{self, Py_ssize_t}; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; +#[cfg(feature = "experimental-inspect")] +use crate::inspect::PyStaticExpr; use crate::instance::Borrowed; use crate::internal_tricks::get_ssize_index; #[cfg(feature = "experimental-inspect")] @@ -614,9 +616,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( &PyTuple::TYPE_HINT, - &[$($T::OUTPUT_TYPE ),+] + &PyStaticExpr::tuple(&[$($T::OUTPUT_TYPE ),+]) ); fn into_pyobject(self, py: Python<'py>) -> Result { @@ -638,9 +640,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( + const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( &PyTuple::TYPE_HINT, - &[$(<&$T>::OUTPUT_TYPE ),+] + &PyStaticExpr::tuple(&[$(<&$T>::OUTPUT_TYPE ),+]) ); fn into_pyobject(self, py: Python<'py>) -> Result { @@ -914,9 +916,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: crate::inspect::PyStaticExpr = crate::inspect::PyStaticExpr::subscript( + const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( &PyTuple::TYPE_HINT, - &[$($T::INPUT_TYPE ),+] + &PyStaticExpr::tuple(&[$($T::INPUT_TYPE ),+]) ); fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs index d5ba1be6acf..b5487676154 100644 --- a/src/types/weakref/proxy.rs +++ b/src/types/weakref/proxy.rs @@ -27,8 +27,8 @@ unsafe impl PyTypeCheck for PyWeakrefProxy { #[cfg(feature = "experimental-inspect")] const TYPE_HINT: PyStaticExpr = PyStaticExpr::bit_or( - &PyStaticExpr::module_attr("weakref", "ProxyType"), - &PyStaticExpr::module_attr("weakref", "CallableProxyType"), + &PyStaticExpr::attribute(&PyStaticExpr::module("weakref"), "ProxyType"), + &PyStaticExpr::attribute(&PyStaticExpr::module("weakref"), "CallableProxyType"), ); #[inline] From ebc8f129b8f1ab09d681bf3594d77250e57a43fb Mon Sep 17 00:00:00 2001 From: Thomas Tanon Date: Wed, 3 Dec 2025 18:49:47 +0100 Subject: [PATCH 03/10] Return type: remove one stage of the specialization (#5673) --- src/impl_/wrap.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/impl_/wrap.rs b/src/impl_/wrap.rs index 9d3372cfe60..3704180cbe4 100644 --- a/src/impl_/wrap.rs +++ b/src/impl_/wrap.rs @@ -21,18 +21,17 @@ impl SomeWrap for Option { } } -// Hierarchy of conversions used in the `IntoPy` implementation +// Hierarchy of conversions used in the function return type machinery pub struct Converter(EmptyTupleConverter); pub struct EmptyTupleConverter(IntoPyObjectConverter); -pub struct IntoPyObjectConverter(IntoPyConverter); -pub struct IntoPyConverter(UnknownReturnResultType); +pub struct IntoPyObjectConverter(UnknownReturnResultType); pub struct UnknownReturnResultType(UnknownReturnType); pub struct UnknownReturnType(PhantomData); pub fn converter(_: &T) -> Converter { - Converter(EmptyTupleConverter(IntoPyObjectConverter(IntoPyConverter( + Converter(EmptyTupleConverter(IntoPyObjectConverter( UnknownReturnResultType(UnknownReturnType(PhantomData)), - )))) + ))) } impl Deref for Converter { @@ -50,13 +49,6 @@ impl Deref for EmptyTupleConverter { } impl Deref for IntoPyObjectConverter { - type Target = IntoPyConverter; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Deref for IntoPyConverter { type Target = UnknownReturnResultType; fn deref(&self) -> &Self::Target { &self.0 From 8df8ac3ef38f1b262d20810c2ef7f79520e691a2 Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Mon, 1 Dec 2025 17:00:30 +0100 Subject: [PATCH 04/10] Draft: Move TypeHint into a Python expression AST Implement a subset of Python expr AST Adds to the "name" construct a kind to distinguish global (global name that does not need to be resolved against the current module) vs local (names that needs to be resolved). This consolidates the previous builtin/local/module construct by taking into account that modules can be relative (for example if in a #[pyclass] the user set `module=` to the submodule name) Adds also the support for `Callable[[int], float]` and a beginning of constructs to represent constants (currently only None but will be useful for typing.Literal support) TODO: - update the macro code - update the introspection code --- src/inspect/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index 936fa5531e7..7d2f18b36fa 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -154,7 +154,7 @@ impl PyStaticExpr { /// ``` /// use pyo3::inspect::PyStaticExpr; /// - /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("list"), &PyStaticExpr::builtin("int"))); + /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("list"), &PyStaticExpr::builtin("int")); /// assert_eq!(T.to_string(), "list[int"); /// ``` pub const fn subscript(value: &'static Self, slice: &'static Self) -> Self { @@ -343,7 +343,7 @@ pub enum PyStaticConstant { // TODO: add Bool(bool), String(&'static str)... (is useful for Literal["foo", "bar"] types) } -/// An operator used in [`PyStaticExpr::BinaryOpt`]. +/// An operator used in [`PyStaticExpr::BinOp`]. #[derive(Clone, Copy)] #[non_exhaustive] pub enum PyStaticOperator { From 8ab1862167c8c149fc07173554d1b381a728a3f2 Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Tue, 9 Dec 2025 15:33:20 +0100 Subject: [PATCH 05/10] Introduces macros --- pyo3-macros-backend/src/pyclass.rs | 14 +- pyo3-macros-backend/src/type_hint.rs | 44 +++-- src/buffer.rs | 5 +- src/conversion.rs | 12 +- src/conversions/std/ipaddr.rs | 16 +- src/conversions/std/map.rs | 38 ++-- src/conversions/std/num.rs | 9 +- src/conversions/std/option.rs | 10 +- src/conversions/std/path.rs | 11 +- src/conversions/std/set.rs | 16 +- src/conversions/std/slice.rs | 5 +- src/impl_/extract_argument.rs | 6 +- src/inspect/mod.rs | 262 ++++++++++++--------------- src/type_object.rs | 4 +- src/types/ellipsis.rs | 5 +- src/types/mapping.rs | 5 +- src/types/mod.rs | 7 +- src/types/none.rs | 6 +- src/types/notimplemented.rs | 5 +- src/types/sequence.rs | 5 +- src/types/tuple.rs | 20 +- src/types/weakref/anyref.rs | 8 +- src/types/weakref/proxy.rs | 8 +- 23 files changed, 240 insertions(+), 281 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 435392bad38..b97cda99354 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -444,11 +444,19 @@ fn get_class_type_hint(cls: &Ident, args: &PyClassArgs, ctx: &Ctx) -> TokenStrea if let Some(module) = &args.options.module { let module = module.value.value(); quote! {{ - const MODULE: #pyo3_path::inspect::PyStaticExpr = #pyo3_path::inspect::PyStaticExpr::module(#module); - #pyo3_path::inspect::PyStaticExpr::attribute(&MODULE, #name) + #pyo3_path::inspect::PyStaticExpr::Attribute { + value: &#pyo3_path::inspect::PyStaticExpr::Name { + id: #module, + kind: #pyo3_path::inspect::PyStaticNameKind::Local + }, + attr: #name + } }} } else { - quote! { #pyo3_path::inspect::PyStaticExpr::local(#name) } + quote! { #pyo3_path::inspect::PyStaticExpr::Name { + id: #name, + kind: #pyo3_path::inspect::PyStaticNameKind::Local + } } } } diff --git a/pyo3-macros-backend/src/type_hint.rs b/pyo3-macros-backend/src/type_hint.rs index 2ccaa05694b..86243a64485 100644 --- a/pyo3-macros-backend/src/type_hint.rs +++ b/pyo3-macros-backend/src/type_hint.rs @@ -121,17 +121,21 @@ impl PythonTypeHint { pub fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { match &self.0 { PythonTypeHintVariant::Local(name) => { - quote! { #pyo3_crate_path::inspect::PyStaticExpr::local(#name) } + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { + id: #name, + kind: #pyo3_crate_path::inspect::PyStaticNameKind::Local + } } } PythonTypeHintVariant::ModuleAttribute { module, attr } => { - if module == "builtins" { - quote! { #pyo3_crate_path::inspect::PyStaticExpr::builtin(#attr) } - } else { - quote! {{ - const MODULE: #pyo3_crate_path::inspect::PyStaticExpr = #pyo3_crate_path::inspect::PyStaticExpr::module(#module); - #pyo3_crate_path::inspect::PyStaticExpr::attribute(&MODULE, #attr) - }} - } + quote! {{ + #pyo3_crate_path::inspect::PyStaticExpr::Attribute { + value: &#pyo3_crate_path::inspect::PyStaticExpr::Name { + id: #module, + kind: #pyo3_crate_path::inspect::PyStaticNameKind::Local + }, + attr: #attr + } + }} } PythonTypeHintVariant::FromPyObject(t) => { quote! { <#t as #pyo3_crate_path::FromPyObject<'_, '_>>::INPUT_TYPE } @@ -156,13 +160,17 @@ impl PythonTypeHint { PythonTypeHintVariant::Type(t) => { quote! { <#t as #pyo3_crate_path::type_object::PyTypeCheck>::TYPE_HINT } } - PythonTypeHintVariant::Union(elements) => { - elements - .iter() - .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)) - .reduce(|left, right| quote! { #pyo3_crate_path::inspect::PyStaticExpr::bit_or(&#left, &#right) }) - .expect("unions must not be empty") - } + PythonTypeHintVariant::Union(elements) => elements + .iter() + .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)) + .reduce(|left, right| { + quote! { #pyo3_crate_path::inspect::PyStaticExpr::BinOp { + left: &#left, + op: #pyo3_crate_path::inspect::PyStaticOperator::BitOr, + right: &#right, + }} + }) + .expect("unions must not be empty"), PythonTypeHintVariant::Subscript { value, slice } => { let value = value.to_introspection_token_stream(pyo3_crate_path); let slice = if slice.len() == 1 { @@ -171,9 +179,9 @@ impl PythonTypeHint { let elts = slice .iter() .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)); - quote! { #pyo3_crate_path::inspect::PyStaticExpr::tuple(&[#(#elts),*]) } + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Tuple { elts: &[#(#elts),*] } } }; - quote! { #pyo3_crate_path::inspect::PyStaticExpr::subscript(&#value, &#slice) } + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Subscript { value: &#value, slice: &#slice } } } } } diff --git a/src/buffer.rs b/src/buffer.rs index eb2625fed4b..753af29faa1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -19,7 +19,7 @@ //! `PyBuffer` implementation #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python}; use crate::{Borrowed, Bound, PyErr}; use std::ffi::{ @@ -202,8 +202,7 @@ impl FromPyObject<'_, '_> for PyBuffer { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Buffer"); + const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("collections.abc", "Buffer"); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { Self::get(&obj) diff --git a/src/conversion.rs b/src/conversion.rs index c67b3ad19b2..9a7d345f983 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -4,7 +4,7 @@ use crate::impl_::pyclass::ExtractPyClassWithClone; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, type_hint_subscript, PyStaticExpr}; use crate::pyclass::boolean_struct::False; use crate::pyclass::{PyClassGuardError, PyClassGuardMutError}; use crate::types::PyTuple; @@ -61,8 +61,7 @@ pub trait IntoPyObject<'py>: Sized { /// For most types, the return value for this method will be identical to that of [`FromPyObject::INPUT_TYPE`]. /// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument. #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); + const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("typing", "Any"); /// Performs the conversion. fn into_pyobject(self, py: Python<'py>) -> Result; @@ -118,7 +117,7 @@ pub trait IntoPyObject<'py>: Sized { #[cfg(feature = "experimental-inspect")] #[doc(hidden)] const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PyList::TYPE_HINT, &Self::OUTPUT_TYPE); + type_hint_subscript!(PyList::TYPE_HINT, Self::OUTPUT_TYPE); } pub(crate) mod private { @@ -417,8 +416,7 @@ pub trait FromPyObject<'a, 'py>: Sized { /// For example, `Vec` would be `collections.abc.Sequence[int]`. /// The default value is `typing.Any`, which is correct for any type. #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); + const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("typing", "Any"); /// Extracts `Self` from the bound smart pointer `obj`. /// @@ -469,7 +467,7 @@ pub trait FromPyObject<'a, 'py>: Sized { #[cfg(feature = "experimental-inspect")] #[doc(hidden)] const SEQUENCE_INPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySequence::TYPE_HINT, &Self::INPUT_TYPE); + type_hint_subscript!(PySequence::TYPE_HINT, Self::INPUT_TYPE); /// Helper used to make a specialized path in extracting `DateTime` where `Tz` is /// `chrono::Local`, which will accept "naive" datetime objects as being in the local timezone. diff --git a/src/conversions/std/ipaddr.rs b/src/conversions/std/ipaddr.rs index ea2eafcc285..cd2f867a51e 100644 --- a/src/conversions/std/ipaddr.rs +++ b/src/conversions/std/ipaddr.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, type_hint_union, PyStaticExpr}; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; @@ -13,9 +13,9 @@ impl FromPyObject<'_, '_> for IpAddr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( - &PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv4Address"), - &PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv6Address"), + const INPUT_TYPE: PyStaticExpr = type_hint_union!( + type_hint_identifier!("ipaddress", "IPv4Address"), + type_hint_identifier!("ipaddress", "IPv6Address") ); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { @@ -43,8 +43,7 @@ impl<'py> IntoPyObject<'py> for Ipv4Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv4Address"); + const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("ipaddress", "IPv4Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV4_ADDRESS: PyOnceLock> = PyOnceLock::new(); @@ -74,8 +73,7 @@ impl<'py> IntoPyObject<'py> for Ipv6Addr { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("ipaddress"), "IPv6Address"); + const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("ipaddress", "IPv6Address"); fn into_pyobject(self, py: Python<'py>) -> Result { static IPV6_ADDRESS: PyOnceLock> = PyOnceLock::new(); @@ -106,7 +104,7 @@ impl<'py> IntoPyObject<'py> for IpAddr { #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::bit_or(&Ipv4Addr::OUTPUT_TYPE, &Ipv6Addr::OUTPUT_TYPE); + type_hint_union!(&Ipv4Addr::OUTPUT_TYPE, &Ipv6Addr::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { match self { diff --git a/src/conversions/std/map.rs b/src/conversions/std/map.rs index 8040abf2304..f4f5fad6596 100644 --- a/src/conversions/std/map.rs +++ b/src/conversions/std/map.rs @@ -1,7 +1,7 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_subscript, PyStaticExpr}; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -23,10 +23,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[K::OUTPUT_TYPE, V::OUTPUT_TYPE]), - ); + const OUTPUT_TYPE: PyStaticExpr = + type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -53,10 +51,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]), - ); + const OUTPUT_TYPE: PyStaticExpr = + type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -82,10 +78,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[K::OUTPUT_TYPE, V::OUTPUT_TYPE]), - ); + const OUTPUT_TYPE: PyStaticExpr = + type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -113,10 +107,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[<&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE]), - ); + const OUTPUT_TYPE: PyStaticExpr = + type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); @@ -141,10 +133,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[K::INPUT_TYPE, V::INPUT_TYPE]), - ); + const INPUT_TYPE: PyStaticExpr = + type_hint_subscript!(&PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; @@ -172,10 +162,8 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyDict::TYPE_HINT, - &PyStaticExpr::tuple(&[K::INPUT_TYPE, V::INPUT_TYPE]), - ); + const INPUT_TYPE: PyStaticExpr = + type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 25c618ddbb3..838e60e96ec 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -4,7 +4,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_subscript, type_hint_union, PyStaticExpr}; use crate::py_result_ext::PyResultExt; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; @@ -324,9 +324,10 @@ impl<'py> FromPyObject<'_, 'py> for u8 { } #[cfg(feature = "experimental-inspect")] - const SEQUENCE_INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( - &PyStaticExpr::bit_or(&PyBytes::TYPE_HINT, &PyByteArray::TYPE_HINT), - &PyStaticExpr::subscript(&PySequence::TYPE_HINT, &Self::INPUT_TYPE), + const SEQUENCE_INPUT_TYPE: PyStaticExpr = type_hint_union!( + PyBytes::TYPE_HINT, + PyByteArray::TYPE_HINT, + type_hint_subscript!(PySequence::TYPE_HINT, Self::INPUT_TYPE) ); } diff --git a/src/conversions/std/option.rs b/src/conversions/std/option.rs index 49541fd2e26..18a764ab4eb 100644 --- a/src/conversions/std/option.rs +++ b/src/conversions/std/option.rs @@ -1,5 +1,9 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_union, PyStaticExpr}; +#[cfg(feature = "experimental-inspect")] +use crate::type_object::PyTypeInfo; +#[cfg(feature = "experimental-inspect")] +use crate::types::PyNone; use crate::{ conversion::IntoPyObject, types::any::PyAnyMethods, BoundObject, FromPyObject, PyAny, Python, }; @@ -13,7 +17,7 @@ where type Output = Bound<'py, Self::Target>; type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::OUTPUT_TYPE, &PyStaticExpr::none()); + const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(&T::OUTPUT_TYPE, PyNone::TYPE_HINT); fn into_pyobject(self, py: Python<'py>) -> Result { self.map_or_else( @@ -49,7 +53,7 @@ where { type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::none()); + const INPUT_TYPE: PyStaticExpr = type_hint_union!(T::INPUT_TYPE, PyNone::TYPE_HINT); fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if obj.is_none() { diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index df167b78663..cfbda3dfe10 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -1,7 +1,7 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, type_hint_union, PyStaticExpr}; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::{ffi, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python}; @@ -13,9 +13,9 @@ impl FromPyObject<'_, '_> for PathBuf { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or( - &OsString::INPUT_TYPE, - &PyStaticExpr::attribute(&PyStaticExpr::module("os"), "PathLike"), + const INPUT_TYPE: PyStaticExpr = type_hint_union!( + OsString::INPUT_TYPE, + type_hint_identifier!("os", "PathLike") ); fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { @@ -31,8 +31,7 @@ impl<'py> IntoPyObject<'py> for &Path { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("pathlib"), "Path"); + const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("pathlib", "Path"); #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { diff --git a/src/conversions/std/set.rs b/src/conversions/std/set.rs index 4821ddb3c6c..9e678f9b7c7 100644 --- a/src/conversions/std/set.rs +++ b/src/conversions/std/set.rs @@ -3,7 +3,7 @@ use std::{cmp, collections, hash}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_subscript, PyStaticExpr}; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -27,7 +27,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::OUTPUT_TYPE); + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -48,8 +48,7 @@ where type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySet::TYPE_HINT, &<&K>::OUTPUT_TYPE); + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -69,7 +68,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::INPUT_TYPE); + const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { @@ -105,7 +104,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::OUTPUT_TYPE); + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self) @@ -127,8 +126,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = - PyStaticExpr::subscript(&PySet::TYPE_HINT, &<&K>::OUTPUT_TYPE); + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { try_new_from_iter(py, self.iter()) @@ -147,7 +145,7 @@ where type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript(&PySet::TYPE_HINT, &K::INPUT_TYPE); + const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index 2ee30791210..6371c493579 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_union, PyStaticExpr}; #[cfg(feature = "experimental-inspect")] use crate::type_object::PyTypeInfo; use crate::{ @@ -66,8 +66,7 @@ impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for Cow<'a, [u8]> { type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = - PyStaticExpr::bit_or(&PyBytes::TYPE_HINT, &PyByteArray::TYPE_HINT); + const INPUT_TYPE: PyStaticExpr = type_hint_union!(PyBytes::TYPE_HINT, PyByteArray::TYPE_HINT); fn extract(ob: crate::Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(bytes) = ob.cast::() { diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index cf93806b27d..37ad75809b8 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -1,5 +1,7 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_union, PyStaticExpr}; +#[cfg(feature = "experimental-inspect")] +use crate::types::PyNone; #[cfg(any(Py_3_10, not(Py_LIMITED_API), feature = "experimental-inspect"))] use crate::types::PyString; use crate::{ @@ -110,7 +112,7 @@ where type Error = T::Error; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::bit_or(&T::INPUT_TYPE, &PyStaticExpr::none()); + const INPUT_TYPE: PyStaticExpr = type_hint_union!(T::INPUT_TYPE, PyNone::TYPE_HINT); #[inline] fn extract( diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index 7d2f18b36fa..83b2954c948 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -6,18 +6,91 @@ use std::fmt::{self, Display, Write}; pub mod types; -/// A Python expression. +/// Builds a type hint from a module name and a member name in the module /// -/// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar) +/// ``` +/// use pyo3::inspect::PyStaticExpr; /// -/// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE). +/// const T: PyStaticExpr = type_hint_identifier!("datetime", "date"); +/// assert_eq!(T.to_string(), "datetime.date"); +/// +/// const T2: PyStaticExpr = type_hint_identifier!("builtins", "int"); +/// assert_eq!(T2.to_string(), "int"); +/// ``` +#[macro_export] +macro_rules! type_hint_identifier { + ("builtins", $name:expr) => { + $crate::inspect::PyStaticExpr::Name { + id: $name, + kind: $crate::inspect::PyStaticNameKind::Global, + } + }; + ($module:expr, $name:expr) => { + $crate::inspect::PyStaticExpr::Attribute { + value: &$crate::inspect::PyStaticExpr::Name { + id: $module, + kind: $crate::inspect::PyStaticNameKind::Global, + }, + attr: $name, + } + }; +} +pub(crate) use type_hint_identifier; + +/// Builds the union of multiple type hints /// /// ``` /// use pyo3::inspect::PyStaticExpr; /// -/// const T: PyStaticExpr = PyStaticExpr::union(&[PyStaticExpr::builtin("int"), PyStaticExpr::attribute(&PyStaticExpr::module("b"), "B")]); -/// assert_eq!(T.to_string(), "int | b.B"); +/// const T: PyStaticExpr = type_hint_union!(type_hint_identifier!("datetime.datetime"), type_hint_identifier!("datetime", "date")); +/// assert_eq!(T.to_string(), "int | float"); +/// ``` +#[macro_export] +macro_rules! type_hint_union { + ($e:expr) => { $e }; + ($l:expr , $($r:expr),+) => { $crate::inspect::PyStaticExpr::BinOp { + left: &$l, + op: $crate::inspect::PyStaticOperator::BitOr, + right: &type_hint_union!($($r),+), + } }; +} +pub(crate) use type_hint_union; + +/// Builds a subscribed type hint +/// /// ``` +/// use pyo3::inspect::PyStaticExpr; +/// +/// const T: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("collections.abc", "Sequence"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T.to_string(), "collections.abc.Sequence[float]"); +/// +/// const 2: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("builtins", "dict"), type_hint_identifier!("builtins", "str"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T.to_string(), "dict[str, float]"); +/// ``` +#[macro_export] +macro_rules! type_hint_subscript { + ($l:expr, $r:expr) => { + $crate::inspect::PyStaticExpr::Subscript { + value: &$l, + slice: &$r + } + }; + ($l:expr, $($r:expr),*) => { + $crate::inspect::PyStaticExpr::Subscript { + value: &$l, + slice: &$crate::inspect::PyStaticExpr::Tuple { elts: &[$($r),*] } + } + }; +} +pub(crate) use type_hint_subscript; + +/// A Python expression. +/// +/// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar) +/// +/// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE). +/// +/// Use macros like [`type_hint_identifier`], [`type_hint_union`] and [`type_hint_subscript`] to construct values. #[derive(Clone, Copy)] #[non_exhaustive] #[allow(missing_docs)] @@ -51,132 +124,6 @@ pub enum PyStaticExpr { }, } -impl PyStaticExpr { - /// A value in the local context. Can be a locally defined class, a module relative to the current one... - #[doc(hidden)] - pub const fn local(id: &'static str) -> Self { - Self::Name { - id, - kind: PyStaticNameKind::Local, - } - } - - /// A global builtin like `int` or `list` - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::builtin("int"); - /// assert_eq!(T.to_string(), "int"); - /// ``` - pub const fn builtin(id: &'static str) -> Self { - Self::Name { - id, - kind: PyStaticNameKind::Global, - } - } - - /// An absolute module name like `datetime` or `collections.abc` - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::module("datetime"); - /// assert_eq!(T.to_string(), "datetime"); - /// ``` - pub const fn module(id: &'static str) -> Self { - Self::Name { - id, - kind: PyStaticNameKind::Global, - } - } - - /// A type contained in a module like `datetime.time` - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"); - /// assert_eq!(T.to_string(), "datetime.time"); - /// ``` - pub const fn attribute(value: &'static Self, attr: &'static str) -> Self { - Self::Attribute { value, attr } - } - - /// The bit or (`|`) operator, also used for type union - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::builtin("float")); - /// assert_eq!(T.to_string(), "int | float"); - /// ``` - pub const fn bit_or(left: &'static Self, right: &'static Self) -> Self { - Self::BinOp { - left, - op: PyStaticOperator::BitOr, - right, - } - } - - /// A tuple - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::tuple(&[PyStaticExpr::builtin("int"), PyStaticExpr::builtin("str")]); - /// assert_eq!(T.to_string(), "(int, str)"); - /// ``` - pub const fn tuple(elts: &'static [Self]) -> Self { - Self::Tuple { elts } - } - - /// A list - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::subscript( - /// &PyStaticExpr::builtin("Callable"), - /// &PyStaticExpr::tuple(&[ - /// &PyStaticExpr::list(&[PyStaticExpr::builtin("int")]), - /// PyStaticExpr::builtin("str") - /// ]) - /// ); - /// assert_eq!(T.to_string(), "Callable[[int], str]"); - /// ``` - pub const fn list(elts: &'static [Self]) -> Self { - Self::List { elts } - } - - /// A subscribed expression - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::subscript(&PyStaticExpr::builtin("list"), &PyStaticExpr::builtin("int")); - /// assert_eq!(T.to_string(), "list[int"); - /// ``` - pub const fn subscript(value: &'static Self, slice: &'static Self) -> Self { - Self::Subscript { value, slice } - } - - /// The `None` constant - /// - /// ``` - /// use pyo3::inspect::PyStaticExpr; - /// - /// const T: PyStaticExpr = PyStaticExpr::none(); - /// assert_eq!(T.to_string(), "None"); - /// ``` - #[doc(hidden)] - pub const fn none() -> Self { - Self::Constant { - value: PyStaticConstant::None, - } - } -} - /// Serialize the type for introspection and return the number of written bytes #[doc(hidden)] pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [u8]) -> usize { @@ -273,7 +220,6 @@ pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { } impl fmt::Display for PyStaticExpr { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Constant { value } => match value { @@ -417,30 +363,48 @@ mod tests { #[test] fn test_to_string() { - const T: PyStaticExpr = PyStaticExpr::subscript( - &PyStaticExpr::builtin("dict"), - &PyStaticExpr::tuple(&[ - PyStaticExpr::bit_or(&PyStaticExpr::builtin("int"), &PyStaticExpr::local("weird")), - PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"), - ]), + const T: PyStaticExpr = type_hint_subscript!( + PyStaticExpr::Name { + id: "dict", + kind: PyStaticNameKind::Global + }, + type_hint_union!( + PyStaticExpr::Name { + id: "int", + kind: PyStaticNameKind::Global + }, + PyStaticExpr::Name { + id: "weird", + kind: PyStaticNameKind::Local + } + ), + type_hint_identifier!("datetime", "time") ); assert_eq!(T.to_string(), "dict[int | weird, datetime.time]") } #[test] fn test_serialize_for_introspection() { - const T: PyStaticExpr = PyStaticExpr::subscript( - &PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Callable"), - &PyStaticExpr::tuple(&[ - PyStaticExpr::list(&[ - PyStaticExpr::bit_or( - &PyStaticExpr::builtin("int"), - &PyStaticExpr::local("weird"), + const T: PyStaticExpr = type_hint_subscript!( + type_hint_identifier!("typing", "Callable"), + PyStaticExpr::List { + elts: &[ + type_hint_union!( + PyStaticExpr::Name { + id: "int", + kind: PyStaticNameKind::Global + }, + PyStaticExpr::Name { + id: "weird", + kind: PyStaticNameKind::Local + } ), - PyStaticExpr::attribute(&PyStaticExpr::module("datetime"), "time"), - ]), - PyStaticExpr::none(), - ]), + type_hint_identifier!("datetime", "time"), + ] + }, + PyStaticExpr::Constant { + value: PyStaticConstant::None + } ); const SER_LEN: usize = serialized_len_for_introspection(&T); const SER: [u8; SER_LEN] = { diff --git a/src/type_object.rs b/src/type_object.rs index 7b1cc6e3987..9a18d3ad953 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -2,7 +2,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::types::{PyAny, PyType}; use crate::{ffi, Bound, Python}; use std::ptr; @@ -58,7 +58,7 @@ pub unsafe trait PyTypeInfo: Sized { /// Provides the full python type as a type hint. #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::attribute(&PyStaticExpr::module("typing"), "Any"); + const TYPE_HINT: PyStaticExpr = type_hint_identifier!("typing", "Any"); /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; diff --git a/src/types/ellipsis.rs b/src/types/ellipsis.rs index 94019064c4c..2bc7dd5f0b0 100644 --- a/src/types/ellipsis.rs +++ b/src/types/ellipsis.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::{ ffi, ffi_ptr_ext::FfiPtrExt, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python, @@ -32,8 +32,7 @@ unsafe impl PyTypeInfo for PyEllipsis { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("types"), "EllipsisType"); + const TYPE_HINT: PyStaticExpr = type_hint_identifier!("types", "EllipsisType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_Ellipsis()) } diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 467433e30ed..fba32b7d701 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -2,7 +2,7 @@ use crate::conversion::IntoPyObject; use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::instance::Bound; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; @@ -28,8 +28,7 @@ unsafe impl PyTypeInfo for PyMapping { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Mapping"); + const TYPE_HINT: PyStaticExpr = type_hint_identifier!("collections.abc", "Mapping"); #[inline] #[allow(clippy::redundant_closure_call)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 3bec2a4bdd6..d2de57d38d4 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -152,12 +152,7 @@ macro_rules! pyobject_type_info_type_hint( #[macro_export] macro_rules! pyobject_type_info_type_hint( ($module:expr, $name:expr) => { - const TYPE_HINT: $crate::inspect::PyStaticExpr = if ::std::matches!($module.as_bytes(), b"builtins") { - $crate::inspect::PyStaticExpr::builtin($name) - } else { - const MODULE: $crate::inspect::PyStaticExpr = $crate::inspect::PyStaticExpr::module($module); - $crate::inspect::PyStaticExpr::attribute(&MODULE, $name) - }; + const TYPE_HINT: $crate::inspect::PyStaticExpr = $crate::type_hint_identifier!($module, $name); }; ); diff --git a/src/types/none.rs b/src/types/none.rs index 4e664de1c51..3011e327914 100644 --- a/src/types/none.rs +++ b/src/types/none.rs @@ -1,6 +1,6 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{PyStaticConstant, PyStaticExpr}; use crate::{ffi, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python}; /// Represents the Python `None` object. @@ -30,7 +30,9 @@ unsafe impl PyTypeInfo for PyNone { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::none(); + const TYPE_HINT: PyStaticExpr = PyStaticExpr::Constant { + value: PyStaticConstant::None, + }; fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_None()) } diff --git a/src/types/notimplemented.rs b/src/types/notimplemented.rs index 1d7a6c980d3..f74ea0f3897 100644 --- a/src/types/notimplemented.rs +++ b/src/types/notimplemented.rs @@ -1,5 +1,5 @@ #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::{ ffi, ffi_ptr_ext::FfiPtrExt, types::any::PyAnyMethods, Borrowed, Bound, PyAny, PyTypeInfo, Python, @@ -32,8 +32,7 @@ unsafe impl PyTypeInfo for PyNotImplemented { const MODULE: Option<&'static str> = None; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("types"), "NotImplementedType"); + const TYPE_HINT: PyStaticExpr = type_hint_identifier!("types", "NotImplementedType"); fn type_object_raw(_py: Python<'_>) -> *mut ffi::PyTypeObject { unsafe { ffi::Py_TYPE(ffi::Py_NotImplemented()) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 4accd0c0c25..dab8c952a0e 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -1,7 +1,7 @@ use crate::err::{self, PyErr, PyResult}; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::instance::Bound; use crate::internal_tricks::get_ssize_index; use crate::py_result_ext::PyResultExt; @@ -27,8 +27,7 @@ unsafe impl PyTypeInfo for PySequence { const MODULE: Option<&'static str> = Some("collections.abc"); #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = - PyStaticExpr::attribute(&PyStaticExpr::module("collections.abc"), "Sequence"); + const TYPE_HINT: PyStaticExpr = type_hint_identifier!("collections.abc", "Sequence"); #[inline] fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index b87cafc5fc4..3b1565b3675 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -3,7 +3,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_subscript, PyStaticExpr}; use crate::instance::Borrowed; use crate::internal_tricks::get_ssize_index; #[cfg(feature = "experimental-inspect")] @@ -616,9 +616,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyTuple::TYPE_HINT, - &PyStaticExpr::tuple(&[$($T::OUTPUT_TYPE ),+]) + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!( + PyTuple::TYPE_HINT, + $($T::OUTPUT_TYPE),+ ); fn into_pyobject(self, py: Python<'py>) -> Result { @@ -640,9 +640,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const OUTPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyTuple::TYPE_HINT, - &PyStaticExpr::tuple(&[$(<&$T>::OUTPUT_TYPE ),+]) + const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!( + PyTuple::TYPE_HINT, + $(<&$T>::OUTPUT_TYPE ),+ ); fn into_pyobject(self, py: Python<'py>) -> Result { @@ -916,9 +916,9 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = PyStaticExpr::subscript( - &PyTuple::TYPE_HINT, - &PyStaticExpr::tuple(&[$($T::INPUT_TYPE ),+]) + const INPUT_TYPE: PyStaticExpr = type_hint_subscript!( + PyTuple::TYPE_HINT, + $($T::INPUT_TYPE ),+ ); fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result diff --git a/src/types/weakref/anyref.rs b/src/types/weakref/anyref.rs index c67bbc4b199..5cd870880a0 100644 --- a/src/types/weakref/anyref.rs +++ b/src/types/weakref/anyref.rs @@ -1,7 +1,7 @@ use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_union, PyStaticExpr}; use crate::sync::PyOnceLock; use crate::type_object::{PyTypeCheck, PyTypeInfo}; use crate::types::any::PyAny; @@ -24,9 +24,9 @@ unsafe impl PyTypeCheck for PyWeakref { const NAME: &'static str = "weakref"; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::bit_or( - &PyWeakrefProxy::TYPE_HINT, - &::TYPE_HINT, + const TYPE_HINT: PyStaticExpr = type_hint_union!( + PyWeakrefProxy::TYPE_HINT, + ::TYPE_HINT ); #[inline] diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs index b5487676154..018b432f845 100644 --- a/src/types/weakref/proxy.rs +++ b/src/types/weakref/proxy.rs @@ -2,7 +2,7 @@ use super::PyWeakrefMethods; use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] -use crate::inspect::PyStaticExpr; +use crate::inspect::{type_hint_identifier, type_hint_union, PyStaticExpr}; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; use crate::type_object::PyTypeCheck; @@ -26,9 +26,9 @@ unsafe impl PyTypeCheck for PyWeakrefProxy { const NAME: &'static str = "weakref.ProxyTypes"; #[cfg(feature = "experimental-inspect")] - const TYPE_HINT: PyStaticExpr = PyStaticExpr::bit_or( - &PyStaticExpr::attribute(&PyStaticExpr::module("weakref"), "ProxyType"), - &PyStaticExpr::attribute(&PyStaticExpr::module("weakref"), "CallableProxyType"), + const TYPE_HINT: PyStaticExpr = type_hint_union!( + type_hint_identifier!("weakref", "ProxyType"), + type_hint_identifier!("weakref", "CallableProxyType") ); #[inline] From 22d90e786601fcd643d3dbd11ecbabcb24f3f80f Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Wed, 10 Dec 2025 16:08:47 +0100 Subject: [PATCH 06/10] Align macros-backend API --- pyo3-macros-backend/src/frompyobject.rs | 10 +- pyo3-macros-backend/src/intopyobject.rs | 10 +- pyo3-macros-backend/src/introspection.rs | 6 +- pyo3-macros-backend/src/pyclass.rs | 4 +- pyo3-macros-backend/src/type_hint.rs | 228 +++++++++++++++-------- src/inspect/mod.rs | 2 +- 6 files changed, 166 insertions(+), 94 deletions(-) diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index 78a8560e4f5..c4478719b69 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -101,7 +101,11 @@ impl<'a> Enum<'a> { #[cfg(feature = "experimental-inspect")] fn input_type(&self) -> PythonTypeHint { - PythonTypeHint::union(self.variants.iter().map(|var| var.input_type())) + self.variants + .iter() + .map(|var| var.input_type()) + .reduce(PythonTypeHint::union) + .expect("Empty enum") } } @@ -463,9 +467,9 @@ impl<'a> Container<'a> { } ContainerType::Tuple(tups) => PythonTypeHint::subscript( PythonTypeHint::builtin("tuple"), - tups.iter().map(|TupleStructField { from_py_with, ty }| { + PythonTypeHint::tuple(tups.iter().map(|TupleStructField { from_py_with, ty }| { Self::field_input_type(from_py_with, ty) - }), + })), ), ContainerType::Struct(_) => { // TODO: implement using a Protocol? diff --git a/pyo3-macros-backend/src/intopyobject.rs b/pyo3-macros-backend/src/intopyobject.rs index eee5d61e8b3..12021a0afb7 100644 --- a/pyo3-macros-backend/src/intopyobject.rs +++ b/pyo3-macros-backend/src/intopyobject.rs @@ -367,12 +367,12 @@ impl<'a, const REF: bool> Container<'a, REF> { } ContainerType::Tuple(tups) => PythonTypeHint::subscript( PythonTypeHint::builtin("tuple"), - tups.iter().map( + PythonTypeHint::tuple(tups.iter().map( |TupleStructField { into_py_with, field, }| { Self::field_output_type(into_py_with, &field.ty) }, - ), + )), ), ContainerType::Struct(_) => { // TODO: implement using a Protocol? @@ -471,7 +471,11 @@ impl<'a, const REF: bool> Enum<'a, REF> { #[cfg(feature = "experimental-inspect")] fn output_type(&self) -> PythonTypeHint { - PythonTypeHint::union(self.variants.iter().map(|var| var.output_type())) + self.variants + .iter() + .map(|var| var.output_type()) + .reduce(PythonTypeHint::union) + .expect("Empty enum") } } diff --git a/pyo3-macros-backend/src/introspection.rs b/pyo3-macros-backend/src/introspection.rs index b9f992ce87c..a6346923097 100644 --- a/pyo3-macros-backend/src/introspection.rs +++ b/pyo3-macros-backend/src/introspection.rs @@ -111,11 +111,11 @@ pub fn function_introspection_code( IntrospectionNode::String(returns.to_python().into()) } else { match returns { - ReturnType::Default => PythonTypeHint::builtin("None"), + ReturnType::Default => PythonTypeHint::none(), ReturnType::Type(_, ty) => match *ty { Type::Tuple(t) if t.elems.is_empty() => { // () is converted to None in return types - PythonTypeHint::builtin("None") + PythonTypeHint::none() } ty => PythonTypeHint::from_return_type(ty, parent), }, @@ -166,7 +166,7 @@ pub fn attribute_introspection_code( if is_final { PythonTypeHint::subscript( PythonTypeHint::module_attr("typing", "Final"), - [PythonTypeHint::from_return_type(rust_type, parent)], + PythonTypeHint::from_return_type(rust_type, parent), ) .into() } else { diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index b97cda99354..7a11c7e017c 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -443,7 +443,7 @@ fn get_class_type_hint(cls: &Ident, args: &PyClassArgs, ctx: &Ctx) -> TokenStrea let name = get_class_python_name(cls, args).to_string(); if let Some(module) = &args.options.module { let module = module.value.value(); - quote! {{ + quote! { #pyo3_path::inspect::PyStaticExpr::Attribute { value: &#pyo3_path::inspect::PyStaticExpr::Name { id: #module, @@ -451,7 +451,7 @@ fn get_class_type_hint(cls: &Ident, args: &PyClassArgs, ctx: &Ctx) -> TokenStrea }, attr: #name } - }} + } } else { quote! { #pyo3_path::inspect::PyStaticExpr::Name { id: #name, diff --git a/pyo3-macros-backend/src/type_hint.rs b/pyo3-macros-backend/src/type_hint.rs index 86243a64485..59110e4dda2 100644 --- a/pyo3-macros-backend/src/type_hint.rs +++ b/pyo3-macros-backend/src/type_hint.rs @@ -8,40 +8,72 @@ use syn::visit_mut::{visit_type_mut, VisitMut}; use syn::{Lifetime, Type}; #[derive(Clone)] -pub struct PythonTypeHint(PythonTypeHintVariant); +pub struct PythonTypeHint(PyExpr); #[derive(Clone)] -enum PythonTypeHintVariant { +enum PyExpr { /// The Python type hint of a FromPyObject implementation - FromPyObject(Type), + FromPyObjectType(Type), /// The Python type hint of a IntoPyObject implementation - IntoPyObject(Type), + IntoPyObjectType(Type), /// The Python type matching the given Rust type given as a function argument ArgumentType(Type), /// The Python type matching the given Rust type given as a function returned value ReturnType(Type), /// The Python type matching the given Rust type Type(Type), - /// A local type - Local(Cow<'static, str>), - /// A type in a module - ModuleAttribute { - module: Cow<'static, str>, + /// A constant like `None` or `123` + Constant { value: PyConstant }, + /// A name + Name { + id: Cow<'static, str>, + kind: PyNameKind, + }, + /// An attribute `value.attr` + Attribute { + value: Box, attr: Cow<'static, str>, }, - /// A union - Union(Vec), - /// A subscript - Subscript { - value: Box, - slice: Vec, + /// A binary operator + BinOp { + left: Box, + op: PyOperator, + right: Box, }, + /// A tuple + Tuple { elts: Vec }, + /// A subscript `value[slice]` + Subscript { value: Box, slice: Box }, +} + +#[derive(Clone, Copy)] +#[non_exhaustive] +pub enum PyNameKind { + /// A local name, relative to the current module + Local, + /// A global name, can be a module like `datetime`, a builtin like `int`... + Global, +} + +#[derive(Clone, Copy)] +enum PyOperator { + /// `|` operator + BitOr, +} + +#[derive(Clone)] +enum PyConstant { + /// None + None, } impl PythonTypeHint { /// Build from a local name pub fn local(name: impl Into>) -> Self { - Self(PythonTypeHintVariant::Local(name.into())) + Self(PyExpr::Name { + id: name.into(), + kind: PyNameKind::Local, + }) } /// Build from a builtins name like `None` @@ -54,9 +86,21 @@ impl PythonTypeHint { module: impl Into>, name: impl Into>, ) -> Self { - Self(PythonTypeHintVariant::ModuleAttribute { - module: module.into(), - attr: name.into(), + let module = module.into(); + let name = name.into(); + Self(if module == "builtins" { + PyExpr::Name { + id: name, + kind: PyNameKind::Global, + } + } else { + PyExpr::Attribute { + value: Box::new(PyExpr::Name { + id: name, + kind: PyNameKind::Global, + }), + attr: module, + } }) } @@ -64,86 +108,83 @@ impl PythonTypeHint { /// /// If self_type is set, self_type will replace Self in the given type pub fn from_from_py_object(t: Type, self_type: Option<&Type>) -> Self { - Self(PythonTypeHintVariant::FromPyObject(clean_type( - t, self_type, - ))) + Self(PyExpr::FromPyObjectType(clean_type(t, self_type))) } /// The type hint of a `IntoPyObject` implementation as a function argument /// /// If self_type is set, self_type will replace Self in the given type pub fn from_into_py_object(t: Type, self_type: Option<&Type>) -> Self { - Self(PythonTypeHintVariant::IntoPyObject(clean_type( - t, self_type, - ))) + Self(PyExpr::IntoPyObjectType(clean_type(t, self_type))) } /// The type hint of the Rust type used as a function argument /// /// If self_type is set, self_type will replace Self in the given type pub fn from_argument_type(t: Type, self_type: Option<&Type>) -> Self { - Self(PythonTypeHintVariant::ArgumentType(clean_type( - t, self_type, - ))) + Self(PyExpr::ArgumentType(clean_type(t, self_type))) } /// The type hint of the Rust type used as a function output type /// /// If self_type is set, self_type will replace Self in the given type pub fn from_return_type(t: Type, self_type: Option<&Type>) -> Self { - Self(PythonTypeHintVariant::ReturnType(clean_type(t, self_type))) + Self(PyExpr::ReturnType(clean_type(t, self_type))) } /// The type hint of the Rust type `PyTypeCheck` trait. /// /// If self_type is set, self_type will replace Self in the given type pub fn from_type(t: Type, self_type: Option<&Type>) -> Self { - Self(PythonTypeHintVariant::Type(clean_type(t, self_type))) + Self(PyExpr::Type(clean_type(t, self_type))) + } + + /// The `None` value. + pub fn none() -> Self { + Self(PyExpr::Constant { + value: PyConstant::None, + }) } /// Build the union of the different element - pub fn union(elements: impl IntoIterator) -> Self { - let elements = elements.into_iter().collect::>(); - if elements.len() == 1 { - return elements.into_iter().next().unwrap(); - } - Self(PythonTypeHintVariant::Union(elements)) + pub fn union(left: Self, right: Self) -> Self { + Self(PyExpr::BinOp { + left: Box::new(left.0), + op: PyOperator::BitOr, + right: Box::new(right.0), + }) } - /// Build the subscripted type value[slice[0], ..., slice[n]] - pub fn subscript(value: Self, slice: impl IntoIterator) -> Self { - Self(crate::type_hint::PythonTypeHintVariant::Subscript { - value: Box::new(value), - slice: slice.into_iter().collect(), + /// Build the subscripted type value[slice] + pub fn subscript(value: Self, slice: Self) -> Self { + Self(PyExpr::Subscript { + value: Box::new(value.0), + slice: Box::new(slice.0), + }) + } + + /// Build a tuple + pub fn tuple(elts: impl IntoIterator) -> Self { + Self(PyExpr::Tuple { + elts: elts.into_iter().map(|e| e.0).collect(), }) } pub fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { - match &self.0 { - PythonTypeHintVariant::Local(name) => { - quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { - id: #name, - kind: #pyo3_crate_path::inspect::PyStaticNameKind::Local - } } - } - PythonTypeHintVariant::ModuleAttribute { module, attr } => { - quote! {{ - #pyo3_crate_path::inspect::PyStaticExpr::Attribute { - value: &#pyo3_crate_path::inspect::PyStaticExpr::Name { - id: #module, - kind: #pyo3_crate_path::inspect::PyStaticNameKind::Local - }, - attr: #attr - } - }} - } - PythonTypeHintVariant::FromPyObject(t) => { + self.0.to_introspection_token_stream(pyo3_crate_path) + } +} + +impl PyExpr { + fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { + match self { + PyExpr::FromPyObjectType(t) => { quote! { <#t as #pyo3_crate_path::FromPyObject<'_, '_>>::INPUT_TYPE } } - PythonTypeHintVariant::IntoPyObject(t) => { + PyExpr::IntoPyObjectType(t) => { quote! { <#t as #pyo3_crate_path::IntoPyObject<'_>>::OUTPUT_TYPE } } - PythonTypeHintVariant::ArgumentType(t) => { + PyExpr::ArgumentType(t) => { quote! { <#t as #pyo3_crate_path::impl_::extract_argument::PyFunctionArgument< { @@ -154,35 +195,58 @@ impl PythonTypeHint { >>::INPUT_TYPE } } - PythonTypeHintVariant::ReturnType(t) => { + PyExpr::ReturnType(t) => { quote! { <#t as #pyo3_crate_path::impl_::introspection::PyReturnType>::OUTPUT_TYPE } } - PythonTypeHintVariant::Type(t) => { + PyExpr::Type(t) => { quote! { <#t as #pyo3_crate_path::type_object::PyTypeCheck>::TYPE_HINT } } - PythonTypeHintVariant::Union(elements) => elements - .iter() - .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)) - .reduce(|left, right| { - quote! { #pyo3_crate_path::inspect::PyStaticExpr::BinOp { + PyExpr::Constant { value } => { + let value = match value { + PyConstant::None => { + quote! { #pyo3_crate_path::inspect::PyStaticConstant::None } + } + }; + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #value } } + } + PyExpr::Name { id, kind } => { + let kind = match kind { + PyNameKind::Local => quote!(#pyo3_crate_path::inspect::PyStaticNameKind::Local), + PyNameKind::Global => { + quote!(#pyo3_crate_path::inspect::PyStaticNameKind::Global) + } + }; + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { id: #id, kind: #kind } } + } + PyExpr::Attribute { value, attr } => { + let value = value.to_introspection_token_stream(pyo3_crate_path); + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Attribute { value: &#value, attr: #attr } } + } + PyExpr::BinOp { left, op, right } => { + let left = left.to_introspection_token_stream(pyo3_crate_path); + let op = match op { + PyOperator::BitOr => quote!(#pyo3_crate_path::inspect::PyStaticOperator::BitOr), + }; + let right = right.to_introspection_token_stream(pyo3_crate_path); + quote! { + #pyo3_crate_path::inspect::PyStaticExpr::BinOp { left: &#left, - op: #pyo3_crate_path::inspect::PyStaticOperator::BitOr, + op: #op, right: &#right, - }} - }) - .expect("unions must not be empty"), - PythonTypeHintVariant::Subscript { value, slice } => { + } + } + } + PyExpr::Subscript { value, slice } => { let value = value.to_introspection_token_stream(pyo3_crate_path); - let slice = if slice.len() == 1 { - slice[0].to_introspection_token_stream(pyo3_crate_path) - } else { - let elts = slice - .iter() - .map(|elt| elt.to_introspection_token_stream(pyo3_crate_path)); - quote! { #pyo3_crate_path::inspect::PyStaticExpr::Tuple { elts: &[#(#elts),*] } } - }; + let slice = slice.to_introspection_token_stream(pyo3_crate_path); quote! { #pyo3_crate_path::inspect::PyStaticExpr::Subscript { value: &#value, slice: &#slice } } } + PyExpr::Tuple { elts } => { + let elts = elts + .iter() + .map(|e| e.to_introspection_token_stream(pyo3_crate_path)); + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Tuple { elts: &[#(#elts),*] } } + } } } } diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index 83b2954c948..c160d417403 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -117,7 +117,7 @@ pub enum PyStaticExpr { Tuple { elts: &'static [Self] }, /// A list List { elts: &'static [Self] }, - /// A subscript `main[*args]` + /// A subscript `value[slice]` Subscript { value: &'static Self, slice: &'static Self, From 1a4580d5854e7e4eaecc92eb7cace44f15c8658e Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Wed, 10 Dec 2025 15:33:41 +0100 Subject: [PATCH 07/10] Cleaner representation for setters --- pyo3-macros-backend/src/pyclass.rs | 15 ++++++- pyo3-macros-backend/src/pyimpl.rs | 8 +++- pyo3-macros-backend/src/type_hint.rs | 61 +++++++--------------------- 3 files changed, 35 insertions(+), 49 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 7a11c7e017c..ed34c3a247f 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1949,7 +1949,20 @@ fn descriptors_to_items( })]), Some("self"), syn::ReturnType::Default, - vec![PythonTypeHint::local(format!("{name}.setter"))], + vec![PythonTypeHint::attribute( + PythonTypeHint::attribute( + PythonTypeHint::from_type( + syn::TypePath { + qself: None, + path: cls.clone().into(), + } + .into(), + None, + ), + name.clone(), + ), + "setter", + )], Some(&parse_quote!(#cls)), )); } diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index db30c0dcaab..3f572521f57 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -405,7 +405,13 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) - } FnType::Setter(_) => { first_argument = Some("self"); - decorators.push(PythonTypeHint::local(format!("{name}.setter"))); + decorators.push(PythonTypeHint::attribute( + PythonTypeHint::attribute( + PythonTypeHint::from_type(parent.clone(), None), + name.clone(), + ), + "setter", + )); } FnType::Fn(_) => { first_argument = Some("self"); diff --git a/pyo3-macros-backend/src/type_hint.rs b/pyo3-macros-backend/src/type_hint.rs index 59110e4dda2..d3ad69e8611 100644 --- a/pyo3-macros-backend/src/type_hint.rs +++ b/pyo3-macros-backend/src/type_hint.rs @@ -25,10 +25,7 @@ enum PyExpr { /// A constant like `None` or `123` Constant { value: PyConstant }, /// A name - Name { - id: Cow<'static, str>, - kind: PyNameKind, - }, + Name { id: Cow<'static, str> }, /// An attribute `value.attr` Attribute { value: Box, @@ -46,15 +43,6 @@ enum PyExpr { Subscript { value: Box, slice: Box }, } -#[derive(Clone, Copy)] -#[non_exhaustive] -pub enum PyNameKind { - /// A local name, relative to the current module - Local, - /// A global name, can be a module like `datetime`, a builtin like `int`... - Global, -} - #[derive(Clone, Copy)] enum PyOperator { /// `|` operator @@ -68,17 +56,16 @@ enum PyConstant { } impl PythonTypeHint { - /// Build from a local name - pub fn local(name: impl Into>) -> Self { - Self(PyExpr::Name { - id: name.into(), - kind: PyNameKind::Local, + /// The `None` value. + pub fn none() -> Self { + Self(PyExpr::Constant { + value: PyConstant::None, }) } /// Build from a builtins name like `None` pub fn builtin(name: impl Into>) -> Self { - Self::module_attr("builtins", name) + Self(PyExpr::Name { id: name.into() }) } /// Build from a module and a name like `collections.abc` and `Sequence` @@ -86,22 +73,7 @@ impl PythonTypeHint { module: impl Into>, name: impl Into>, ) -> Self { - let module = module.into(); - let name = name.into(); - Self(if module == "builtins" { - PyExpr::Name { - id: name, - kind: PyNameKind::Global, - } - } else { - PyExpr::Attribute { - value: Box::new(PyExpr::Name { - id: name, - kind: PyNameKind::Global, - }), - attr: module, - } - }) + Self::attribute(Self(PyExpr::Name { id: module.into() }), name) } /// The type hint of a `FromPyObject` implementation as a function argument @@ -139,10 +111,11 @@ impl PythonTypeHint { Self(PyExpr::Type(clean_type(t, self_type))) } - /// The `None` value. - pub fn none() -> Self { - Self(PyExpr::Constant { - value: PyConstant::None, + /// An attribute of a given value: `value.attr` + pub fn attribute(value: Self, attr: impl Into>) -> Self { + Self(PyExpr::Attribute { + value: Box::new(value.0), + attr: attr.into(), }) } @@ -209,14 +182,8 @@ impl PyExpr { }; quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #value } } } - PyExpr::Name { id, kind } => { - let kind = match kind { - PyNameKind::Local => quote!(#pyo3_crate_path::inspect::PyStaticNameKind::Local), - PyNameKind::Global => { - quote!(#pyo3_crate_path::inspect::PyStaticNameKind::Global) - } - }; - quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { id: #id, kind: #kind } } + PyExpr::Name { id } => { + quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { id: #id, kind: #pyo3_crate_path::inspect::PyStaticNameKind::Global } } } PyExpr::Attribute { value, attr } => { let value = value.to_introspection_token_stream(pyo3_crate_path); From a3d7c06ad735ac95077b374d70ed2c4ba906f1bc Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Wed, 10 Dec 2025 15:33:41 +0100 Subject: [PATCH 08/10] Implement the introspection side --- pyo3-introspection/src/introspection.rs | 203 +++++++------ pyo3-introspection/src/model.rs | 70 +++-- pyo3-introspection/src/stubs.rs | 380 ++++++++++++++---------- src/inspect/mod.rs | 12 +- 4 files changed, 381 insertions(+), 284 deletions(-) diff --git a/pyo3-introspection/src/introspection.rs b/pyo3-introspection/src/introspection.rs index 65b7115e60d..8bab0c4d2cd 100644 --- a/pyo3-introspection/src/introspection.rs +++ b/pyo3-introspection/src/introspection.rs @@ -1,6 +1,6 @@ use crate::model::{ - Argument, Arguments, Attribute, Class, Function, Module, PythonIdentifier, TypeHint, - TypeHintExpr, VariableLengthArgument, + Argument, Arguments, Attribute, Class, Constant, Expr, Function, Module, NameKind, Operator, + TypeHint, VariableLengthArgument, }; use anyhow::{anyhow, bail, ensure, Context, Result}; use goblin::elf::section_header::SHN_XINDEX; @@ -13,6 +13,7 @@ use goblin::Object; use serde::de::value::MapAccessDeserializer; use serde::de::{Error, MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; +use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashMap; use std::path::Path; @@ -144,7 +145,7 @@ fn convert_members<'a>( parent: _, decorators, returns, - } => functions.push(convert_function(name, arguments, decorators, returns)?), + } => functions.push(convert_function(name, arguments, decorators, returns)), Chunk::Attribute { name, id: _, @@ -159,25 +160,22 @@ fn convert_members<'a>( classes.sort_by(|l, r| l.name.cmp(&r.name)); functions.sort_by(|l, r| match l.name.cmp(&r.name) { Ordering::Equal => { - // We put the getter before the setter. For that, we put @property before the other ones - if l.decorators - .iter() - .any(|d| d.name == "property" && d.module.as_deref() == Some("builtins")) - { - Ordering::Less - } else if r - .decorators - .iter() - .any(|d| d.name == "property" && d.module.as_deref() == Some("builtins")) - { - Ordering::Greater - } else { - // We pick an ordering based on decorators - l.decorators - .iter() - .map(|d| &d.name) - .cmp(r.decorators.iter().map(|d| &d.name)) + fn decorator_expr_key(expr: &Expr) -> (u32, Cow<'_, str>) { + // We put plain names before attributes for @property to be before @foo.property + match expr { + Expr::Name { id, .. } => (0, Cow::Borrowed(id)), + Expr::Attribute { value, attr } => { + let (c, v) = decorator_expr_key(value); + (c + 1, Cow::Owned(format!("{v}.{attr}"))) + } + _ => (u32::MAX, Cow::Borrowed("")), // We don't care + } } + // We pick an ordering based on decorators + l.decorators + .iter() + .map(decorator_expr_key) + .cmp(r.decorators.iter().map(decorator_expr_key)) } o => o, }); @@ -188,8 +186,8 @@ fn convert_members<'a>( fn convert_class( id: &str, name: &str, - bases: &[ChunkTypeHint], - decorators: &[ChunkTypeHint], + bases: &[ChunkExpr], + decorators: &[ChunkExpr], chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, ) -> Result { @@ -208,47 +206,22 @@ fn convert_class( ); Ok(Class { name: name.into(), - bases: bases - .iter() - .map(convert_python_identifier) - .collect::>()?, + bases: bases.iter().map(convert_expr).collect(), methods, attributes, - decorators: decorators - .iter() - .map(convert_python_identifier) - .collect::>()?, + decorators: decorators.iter().map(convert_expr).collect(), }) } -fn convert_python_identifier(decorator: &ChunkTypeHint) -> Result { - match convert_type_hint(decorator) { - TypeHint::Plain(id) => Ok(PythonIdentifier { - module: None, - name: id.clone(), - }), - TypeHint::Ast(expr) => { - if let TypeHintExpr::Identifier(i) = expr { - Ok(i) - } else { - bail!("PyO3 introspection currently only support decorators that are identifiers of a Python function, got {expr:?}") - } - } - } -} - fn convert_function( name: &str, arguments: &ChunkArguments, - decorators: &[ChunkTypeHint], + decorators: &[ChunkExpr], returns: &Option, -) -> Result { - Ok(Function { +) -> Function { + Function { name: name.into(), - decorators: decorators - .iter() - .map(convert_python_identifier) - .collect::>()?, + decorators: decorators.iter().map(convert_expr).collect(), arguments: Arguments { positional_only_arguments: arguments.posonlyargs.iter().map(convert_argument).collect(), arguments: arguments.args.iter().map(convert_argument).collect(), @@ -263,7 +236,7 @@ fn convert_function( .map(convert_variable_length_argument), }, returns: returns.as_ref().map(convert_type_hint), - }) + } } fn convert_argument(arg: &ChunkArgument) -> Argument { @@ -295,34 +268,45 @@ fn convert_attribute( fn convert_type_hint(arg: &ChunkTypeHint) -> TypeHint { match arg { - ChunkTypeHint::Ast(expr) => TypeHint::Ast(convert_type_hint_expr(expr)), + ChunkTypeHint::Ast(expr) => TypeHint::Ast(convert_expr(expr)), ChunkTypeHint::Plain(t) => TypeHint::Plain(t.clone()), } } -fn convert_type_hint_expr(expr: &ChunkTypeHintExpr) -> TypeHintExpr { +fn convert_expr(expr: &ChunkExpr) -> Expr { match expr { - ChunkTypeHintExpr::Local { id } => PythonIdentifier { - module: None, - name: id.clone(), - } - .into(), - ChunkTypeHintExpr::Builtin { id } => PythonIdentifier { - module: Some("builtins".into()), - name: id.clone(), - } - .into(), - ChunkTypeHintExpr::Attribute { module, attr } => PythonIdentifier { - module: Some(module.clone()), - name: attr.clone(), - } - .into(), - ChunkTypeHintExpr::Union { elts } => { - TypeHintExpr::Union(elts.iter().map(convert_type_hint_expr).collect()) - } - ChunkTypeHintExpr::Subscript { value, slice } => TypeHintExpr::Subscript { - value: Box::new(convert_type_hint_expr(value)), - slice: slice.iter().map(convert_type_hint_expr).collect(), + ChunkExpr::Name { id, kind } => Expr::Name { + id: id.clone(), + kind: match kind { + ChunkNameKind::Local => NameKind::Local, + ChunkNameKind::Global => NameKind::Global, + }, + }, + ChunkExpr::Attribute { value, attr } => Expr::Attribute { + value: Box::new(convert_expr(value)), + attr: attr.clone(), + }, + ChunkExpr::BinOp { left, op, right } => Expr::BinOp { + left: Box::new(convert_expr(left)), + op: match op { + ChunkOperator::BitOr => Operator::BitOr, + }, + right: Box::new(convert_expr(right)), + }, + ChunkExpr::Subscript { value, slice } => Expr::Subscript { + value: Box::new(convert_expr(value)), + slice: Box::new(convert_expr(slice)), + }, + ChunkExpr::Tuple { elts } => Expr::Tuple { + elts: elts.iter().map(convert_expr).collect(), + }, + ChunkExpr::List { elts } => Expr::List { + elts: elts.iter().map(convert_expr).collect(), + }, + ChunkExpr::Constant { value } => Expr::Constant { + value: match value { + ChunkConstant::None => Constant::None, + }, }, } } @@ -469,9 +453,9 @@ enum Chunk { id: String, name: String, #[serde(default)] - bases: Vec, + bases: Vec, #[serde(default)] - decorators: Vec, + decorators: Vec, }, Function { #[serde(default)] @@ -481,7 +465,7 @@ enum Chunk { #[serde(default)] parent: Option, #[serde(default)] - decorators: Vec, + decorators: Vec, #[serde(default)] returns: Option, }, @@ -525,7 +509,7 @@ struct ChunkArgument { /// /// We keep separated type to allow them to evolve independently (this type will need to handle backward compatibility). enum ChunkTypeHint { - Ast(ChunkTypeHintExpr), + Ast(ChunkExpr), Plain(String), } @@ -570,22 +554,45 @@ impl<'de> Deserialize<'de> for ChunkTypeHint { #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] -enum ChunkTypeHintExpr { - Local { - id: String, - }, - Builtin { - id: String, - }, - Attribute { - module: String, - attr: String, - }, - Union { - elts: Vec, +enum ChunkExpr { + /// A constant like `None` or `123` + Constant { + #[serde(flatten)] + value: ChunkConstant, }, - Subscript { - value: Box, - slice: Vec, + /// A name + Name { id: String, kind: ChunkNameKind }, + /// An attribute `value.attr` + Attribute { value: Box, attr: String }, + /// A binary operator + BinOp { + left: Box, + op: ChunkOperator, + right: Box, }, + /// A tuple + Tuple { elts: Vec }, + /// A list + List { elts: Vec }, + /// A subscript `value[slice]` + Subscript { value: Box, slice: Box }, +} + +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +enum ChunkNameKind { + Local, + Global, +} + +#[derive(Deserialize)] +#[serde(tag = "kind", rename_all = "lowercase")] +pub enum ChunkConstant { + None, +} + +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ChunkOperator { + BitOr, } diff --git a/pyo3-introspection/src/model.rs b/pyo3-introspection/src/model.rs index 9fba19c5470..ef240fe44d1 100644 --- a/pyo3-introspection/src/model.rs +++ b/pyo3-introspection/src/model.rs @@ -11,18 +11,18 @@ pub struct Module { #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Class { pub name: String, - pub bases: Vec, + pub bases: Vec, pub methods: Vec, pub attributes: Vec, /// decorator like 'typing.final' - pub decorators: Vec, + pub decorators: Vec, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Function { pub name: String, /// decorator like 'property' or 'staticmethod' - pub decorators: Vec, + pub decorators: Vec, pub arguments: Arguments, /// return type pub returns: Option, @@ -73,34 +73,56 @@ pub struct VariableLengthArgument { /// Might be a plain string or an AST fragment #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum TypeHint { - Ast(TypeHintExpr), + Ast(Expr), Plain(String), } -/// A type hint annotation as an AST fragment +/// A python expression +/// +/// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar) #[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum TypeHintExpr { - /// An identifier - Identifier(PythonIdentifier), - /// A union `{left} | {right}` - Union(Vec), - /// A subscript `{value}[*slice]` - Subscript { - value: Box, - slice: Vec, +pub enum Expr { + /// A constant like `None` or `123` + Constant { value: Constant }, + /// A name + Name { id: String, kind: NameKind }, + /// An attribute `value.attr` + Attribute { value: Box, attr: String }, + /// A binary operator + BinOp { + left: Box, + op: Operator, + right: Box, }, + /// A tuple + Tuple { elts: Vec }, + /// A list + List { elts: Vec }, + /// A subscript `main[*args]` + Subscript { value: Box, slice: Box }, } -impl From for TypeHintExpr { - #[inline] - fn from(value: PythonIdentifier) -> Self { - Self::Identifier(value) - } +/// A PyO3 extension to the Python AST to know more about [`Expr::Name`]. +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub enum NameKind { + /// A local name, relative to the current module + Local, + /// A global name, can be a module like `datetime`, a builtin like `int`... + Global, } -/// An Python identifier, either local (with `module = None`) or global (with `module = Some(_)`) -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub struct PythonIdentifier { - pub module: Option, - pub name: String, +/// A PyO3 extension to the Python AST to know more about [`Expr::Constant`]. +/// +/// This enables advanced features like escaping. +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub enum Constant { + /// None + None, +} + +/// An operator used in [`Expr::BinOp`]. +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub enum Operator { + /// `|` operator + BitOr, } diff --git a/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index f8b094039b3..6924db0ad9d 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -1,6 +1,6 @@ use crate::model::{ - Argument, Arguments, Attribute, Class, Function, Module, PythonIdentifier, TypeHint, - TypeHintExpr, VariableLengthArgument, + Argument, Arguments, Attribute, Class, Constant, Expr, Function, Module, NameKind, Operator, + TypeHint, VariableLengthArgument, }; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::path::PathBuf; @@ -69,25 +69,22 @@ fn module_stubs(module: &Module, parents: &[&str]) -> String { arguments: vec![Argument { name: "name".to_string(), default_value: None, - annotation: Some(TypeHint::Ast( - PythonIdentifier { - module: Some("builtins".into()), - name: "str".into(), - } - .into(), - )), + annotation: Some(TypeHint::Ast(Expr::Name { + id: "str".into(), + kind: NameKind::Global, + })), }], vararg: None, keyword_only_arguments: Vec::new(), kwarg: None, }, - returns: Some(TypeHint::Ast( - PythonIdentifier { - module: Some("_typeshed".into()), - name: "Incomplete".into(), - } - .into(), - )), + returns: Some(TypeHint::Ast(Expr::Attribute { + value: Box::new(Expr::Name { + id: "_typeshed".into(), + kind: NameKind::Global, + }), + attr: "Incomplete".into(), + })), }, &imports, )); @@ -122,7 +119,7 @@ fn class_stubs(class: &Class, imports: &Imports) -> String { let mut buffer = String::new(); for decorator in &class.decorators { buffer.push('@'); - imports.serialize_identifier(decorator, &mut buffer); + imports.serialize_expr(decorator, &mut buffer); buffer.push('\n'); } buffer.push_str("class "); @@ -133,7 +130,7 @@ fn class_stubs(class: &Class, imports: &Imports) -> String { if i > 0 { buffer.push_str(", "); } - imports.serialize_identifier(base, &mut buffer); + imports.serialize_expr(base, &mut buffer); } buffer.push(')'); } @@ -187,7 +184,7 @@ fn function_stubs(function: &Function, imports: &Imports) -> String { let mut buffer = String::new(); for decorator in &function.decorators { buffer.push('@'); - imports.serialize_identifier(decorator, &mut buffer); + imports.serialize_expr(decorator, &mut buffer); buffer.push('\n'); } buffer.push_str("def "); @@ -244,7 +241,7 @@ fn variable_length_argument_stub(argument: &VariableLengthArgument, imports: &Im fn type_hint_stub(type_hint: &TypeHint, imports: &Imports, buffer: &mut String) { match type_hint { - TypeHint::Ast(t) => imports.serialize_type_hint(t, buffer), + TypeHint::Ast(t) => imports.serialize_expr(t, buffer), TypeHint::Plain(t) => buffer.push_str(t), } } @@ -255,7 +252,7 @@ struct Imports { /// Import lines ready to use imports: Vec, /// Renaming map: from module name and member name return the name to use in type hints - renaming: BTreeMap<(String, String), String>, + renaming: BTreeMap<(Option, String), String>, } impl Imports { @@ -274,19 +271,25 @@ impl Imports { let mut renaming = BTreeMap::new(); let mut local_name_to_module_and_attribute = BTreeMap::new(); - // We first process local and built-ins elements, they are never aliased or imported + // We first process local elements, they are never aliased or imported for name in module .classes .iter() .map(|c| c.name.clone()) .chain(module.functions.iter().map(|f| f.name.clone())) .chain(module.attributes.iter().map(|a| a.name.clone())) - .chain(elements_used_in_annotations.locals) + .chain( + elements_used_in_annotations + .locals + .remove(&None) + .into_iter() + .flatten(), + ) { local_name_to_module_and_attribute.insert(name.clone(), (None, name.clone())); } - // We compute the set of ways the current module can be named + // We compute the set of ways the current module or its parents can be named let mut possible_current_module_names = vec![module.name.clone()]; let mut current_module_name = Some(module.name.clone()); for parent in module_parents.iter().rev() { @@ -300,11 +303,19 @@ impl Imports { } // We process then imports, normalizing local imports - for (module, attrs) in elements_used_in_annotations.module_members { - let normalized_module = if possible_current_module_names.contains(&module) { - None + for (module, attrs) in elements_used_in_annotations + .locals + .iter() + .chain(&elements_used_in_annotations.globals) + { + let normalized_module = if let Some(module) = module { + if possible_current_module_names.contains(module) { + None + } else { + Some(module.clone()) + } } else { - Some(module.clone()) + Some("builtins".into()) // This is fine because we have removed the local identifiers }; let mut import_for_module = Vec::new(); for attr in attrs { @@ -371,60 +382,96 @@ impl Imports { } } } + imports.sort(); // We make sure they are sorted Self { imports, renaming } } - fn serialize_type_hint(&self, expr: &TypeHintExpr, buffer: &mut String) { + fn serialize_expr(&self, expr: &Expr, buffer: &mut String) { match expr { - TypeHintExpr::Identifier(id) => { - self.serialize_identifier(id, buffer); + Expr::Constant { value } => match value { + Constant::None => buffer.push_str("None"), + }, + Expr::Name { id, kind } => { + buffer.push_str(match kind { + NameKind::Global => self + .renaming + .get(&(None, id.clone())) + .expect("All type hint attributes should have been visited"), + NameKind::Local => id, + }); } - TypeHintExpr::Union(elts) => { - for (i, elt) in elts.iter().enumerate() { - if i > 0 { - buffer.push_str(" | "); - } - self.serialize_type_hint(elt, buffer); + Expr::Attribute { value, attr } => { + if let Expr::Name { id, .. } = &**value { + buffer.push_str( + self.renaming + .get(&(Some(id.clone()), attr.clone())) + .expect("All type hint attributes should have been visited"), + ); + } else { + self.serialize_expr(value, buffer); + buffer.push('.'); + buffer.push_str(attr); + } + } + Expr::BinOp { left, op, right } => { + self.serialize_expr(left, buffer); + buffer.push(' '); + buffer.push(match op { + Operator::BitOr => '|', + }); + self.serialize_expr(right, buffer); + } + Expr::Tuple { elts } => { + buffer.push('('); + self.serialize_elts(elts, buffer); + if elts.len() == 1 { + buffer.push(','); } + buffer.push(')') } - TypeHintExpr::Subscript { value, slice } => { - self.serialize_type_hint(value, buffer); + Expr::List { elts } => { buffer.push('['); - for (i, elt) in slice.iter().enumerate() { - if i > 0 { - buffer.push_str(", "); - } - self.serialize_type_hint(elt, buffer); + self.serialize_elts(elts, buffer); + buffer.push(']') + } + Expr::Subscript { value, slice } => { + self.serialize_expr(value, buffer); + buffer.push('['); + if let Expr::Tuple { elts } = &**slice { + // We don't display the tuple parentheses + self.serialize_elts(elts, buffer); + } else { + self.serialize_expr(slice, buffer); } buffer.push(']'); } } } - fn serialize_identifier(&self, id: &PythonIdentifier, buffer: &mut String) { - buffer.push_str(if let Some(module) = &id.module { - self.renaming - .get(&(module.clone(), id.name.clone())) - .expect("All type hint attributes should have been visited") - } else { - &id.name - }); + fn serialize_elts(&self, elts: &[Expr], buffer: &mut String) { + for (i, elt) in elts.iter().enumerate() { + if i > 0 { + buffer.push_str(", "); + } + self.serialize_expr(elt, buffer); + } } } /// Lists all the elements used in annotations struct ElementsUsedInAnnotations { - /// module -> name - module_members: BTreeMap>, - locals: BTreeSet, + /// module -> name where module is global (from the root of the interpreter). `None` for builtins. + globals: BTreeMap, BTreeSet>, + /// module -> name where module is local (relative to current module). `None` for elements of the current module. + locals: BTreeMap, BTreeSet>, } impl ElementsUsedInAnnotations { fn new() -> Self { Self { - module_members: BTreeMap::new(), - locals: BTreeSet::new(), + globals: BTreeMap::new(), + locals: BTreeMap::new(), } } @@ -439,12 +486,9 @@ impl ElementsUsedInAnnotations { self.walk_function(function); } if module.incomplete { - self.module_members - .entry("builtins".into()) - .or_default() - .insert("str".into()); - self.module_members - .entry("_typeshed".into()) + self.globals.entry(None).or_default().insert("str".into()); + self.globals + .entry(Some("_typeshed".into())) .or_default() .insert("Incomplete".into()); } @@ -452,10 +496,10 @@ impl ElementsUsedInAnnotations { fn walk_class(&mut self, class: &Class) { for base in &class.bases { - self.walk_identifier(base); + self.walk_expr(base); } for decorator in &class.decorators { - self.walk_identifier(decorator); + self.walk_expr(decorator); } for method in &class.methods { self.walk_function(method); @@ -473,7 +517,7 @@ impl ElementsUsedInAnnotations { fn walk_function(&mut self, function: &Function) { for decorator in &function.decorators { - self.walk_identifier(decorator); + self.walk_expr(decorator); } for arg in function .arguments @@ -504,37 +548,48 @@ impl ElementsUsedInAnnotations { fn walk_type_hint(&mut self, type_hint: &TypeHint) { if let TypeHint::Ast(type_hint) = type_hint { - self.walk_type_hint_expr(type_hint); + self.walk_expr(type_hint); } } - fn walk_type_hint_expr(&mut self, expr: &TypeHintExpr) { + fn walk_expr(&mut self, expr: &Expr) { match expr { - TypeHintExpr::Identifier(id) => { - self.walk_identifier(id); + Expr::Name { id, kind } => { + (match kind { + NameKind::Global => &mut self.globals, + NameKind::Local => &mut self.locals, + }) + .entry(None) + .or_default() + .insert(id.clone()); } - TypeHintExpr::Union(elts) => { - for elt in elts { - self.walk_type_hint_expr(elt) + Expr::Attribute { value, attr } => { + if let Expr::Name { id, kind } = &**value { + (match kind { + NameKind::Global => &mut self.globals, + NameKind::Local => &mut self.locals, + }) + .entry(Some(id.into())) + .or_default() + .insert(attr.clone()); + } else { + self.walk_expr(value) } } - TypeHintExpr::Subscript { value, slice } => { - self.walk_type_hint_expr(value); - for elt in slice { - self.walk_type_hint_expr(elt); + Expr::BinOp { left, right, .. } => { + self.walk_expr(left); + self.walk_expr(right); + } + Expr::Subscript { value, slice } => { + self.walk_expr(value); + self.walk_expr(slice); + } + Expr::Tuple { elts } | Expr::List { elts } => { + for elt in elts { + self.walk_expr(elt) } } - } - } - - fn walk_identifier(&mut self, id: &PythonIdentifier) { - if let Some(module) = &id.module { - self.module_members - .entry(module.clone()) - .or_default() - .insert(id.name.clone()); - } else { - self.locals.insert(id.name.clone()); + Expr::Constant { .. } => (), } } } @@ -616,63 +671,76 @@ mod tests { #[test] fn test_import() { - let big_type = TypeHintExpr::Subscript { - value: Box::new( - PythonIdentifier { - module: Some("builtins".into()), - name: "dict".into(), - } - .into(), - ), - slice: vec![ - PythonIdentifier { - module: Some("foo.bar".into()), - name: "A".into(), - } - .into(), - TypeHintExpr::Union(vec![ - PythonIdentifier { - module: Some("bar".into()), - name: "A".into(), - } - .into(), - PythonIdentifier { - module: Some("foo".into()), - name: "A.C".into(), - } - .into(), - PythonIdentifier { - module: Some("foo".into()), - name: "A.D".into(), - } - .into(), - PythonIdentifier { - module: Some("foo".into()), - name: "B".into(), - } - .into(), - PythonIdentifier { - module: Some("bat".into()), - name: "A".into(), - } - .into(), - PythonIdentifier { - module: None, - name: "int".into(), - } - .into(), - PythonIdentifier { - module: Some("builtins".into()), - name: "int".into(), - } - .into(), - PythonIdentifier { - module: Some("builtins".into()), - name: "float".into(), - } - .into(), - ]), - ], + let big_type = Expr::Subscript { + value: Box::new(Expr::Name { + id: "dict".into(), + kind: NameKind::Global, + }), + slice: Box::new(Expr::Tuple { + elts: vec![ + Expr::Attribute { + value: Box::new(Expr::Name { + id: "foo.bar".into(), + kind: NameKind::Local, + }), + attr: "A".into(), + }, + Expr::Tuple { + elts: vec![ + Expr::Attribute { + value: Box::new(Expr::Name { + id: "bar".into(), + kind: NameKind::Local, + }), + attr: "A".into(), + }, + Expr::Attribute { + value: Box::new(Expr::Name { + id: "foo".into(), + kind: NameKind::Local, + }), + attr: "A.C".into(), + }, + Expr::Attribute { + value: Box::new(Expr::Attribute { + value: Box::new(Expr::Name { + id: "foo".into(), + kind: NameKind::Local, + }), + attr: "A".into(), + }), + attr: "D".into(), + }, + Expr::Attribute { + value: Box::new(Expr::Name { + id: "foo".into(), + kind: NameKind::Local, + }), + attr: "B".into(), + }, + Expr::Attribute { + value: Box::new(Expr::Name { + id: "bat".into(), + kind: NameKind::Local, + }), + attr: "A".into(), + }, + Expr::Name { + id: "int".into(), + kind: NameKind::Local, + }, + Expr::Name { + id: "int".into(), + kind: NameKind::Global, + }, + Expr::Name { + id: "float".into(), + kind: NameKind::Global, + }, + ], + }, + ], + }), }; let imports = Imports::create( &Module { @@ -680,15 +748,18 @@ mod tests { modules: Vec::new(), classes: vec![Class { name: "A".into(), - bases: vec![PythonIdentifier { - module: Some("builtins".into()), - name: "dict".into(), + bases: vec![Expr::Name { + id: "dict".into(), + kind: NameKind::Global, }], methods: Vec::new(), attributes: Vec::new(), - decorators: vec![PythonIdentifier { - module: Some("typing".into()), - name: "final".into(), + decorators: vec![Expr::Attribute { + value: Box::new(Expr::Name { + id: "typing".into(), + kind: NameKind::Global, + }), + attr: "final".into(), }], }], functions: vec![Function { @@ -719,10 +790,7 @@ mod tests { ] ); let mut output = String::new(); - imports.serialize_type_hint(&big_type, &mut output); - assert_eq!( - output, - "dict[A, A | A3.C | A3.D | B | A2 | int | int2 | float]" - ); + imports.serialize_expr(&big_type, &mut output); + assert_eq!(output, "dict[A, (A, A3.C, A3.D, B, A2, int, int2, float)]"); } } diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index c160d417403..733ed43d9bc 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -138,7 +138,7 @@ pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [ } }, PyStaticExpr::Name { id, kind } => { - output = write_slice_and_move_forward(b"{\"type\":\"", output); + output = write_slice_and_move_forward(b"{\"type\":\"name\",\"kind\":\"", output); output = write_slice_and_move_forward( match kind { PyStaticNameKind::Local => b"local", @@ -197,8 +197,8 @@ pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { }, PyStaticExpr::Name { id, kind } => { (match kind { - PyStaticNameKind::Local => 24, - PyStaticNameKind::Global => 25, + PyStaticNameKind::Local => 38, + PyStaticNameKind::Global => 39, }) + id.len() } PyStaticExpr::Attribute { value, attr } => { @@ -255,14 +255,14 @@ impl fmt::Display for PyStaticExpr { } Self::Subscript { value, slice } => { value.fmt(f)?; - f.write_str("[")?; + f.write_char('[')?; if let PyStaticExpr::Tuple { elts } = slice { // We don't display the tuple parentheses fmt_elements(elts, f)?; } else { slice.fmt(f)?; } - f.write_str("]") + f.write_char(']') } } } @@ -414,7 +414,7 @@ mod tests { }; assert_eq!( std::str::from_utf8(&SER).unwrap(), - r#"{"type":"subscript","value":{"type":"attribute","value":{"type":"global","id":"typing"},"attr":"Callable"},"slice":{"type":"tuple","elts":[{"type":"list","elts":[{"type":"binop","left":{"type":"global","id":"int"},"op":"bitor","right":{"type":"local","id":"weird"}},{"type":"attribute","value":{"type":"global","id":"datetime"},"attr":"time"}]},{"type":"constant","kind":"none"}]}}"# + r#"{"type":"subscript","value":{"type":"attribute","value":{"type":"name","kind":"global","id":"typing"},"attr":"Callable"},"slice":{"type":"tuple","elts":[{"type":"list","elts":[{"type":"binop","left":{"type":"name","kind":"global","id":"int"},"op":"bitor","right":{"type":"name","kind":"local","id":"weird"}},{"type":"attribute","value":{"type":"name","kind":"global","id":"datetime"},"attr":"time"}]},{"type":"constant","kind":"none"}]}}"# ) } } From dac34fb447ae40bfc530ae303a1ee3eb5fecc3d9 Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Fri, 12 Dec 2025 15:44:48 +0100 Subject: [PATCH 09/10] Fixes tests --- newsfragments/5671.changed.md | 1 + pyo3-introspection/src/introspection.rs | 2 +- pyo3-introspection/src/stubs.rs | 5 +- src/inspect/mod.rs | 93 ++++++++++++++++--------- 4 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 newsfragments/5671.changed.md diff --git a/newsfragments/5671.changed.md b/newsfragments/5671.changed.md new file mode 100644 index 00000000000..e1647ff4b20 --- /dev/null +++ b/newsfragments/5671.changed.md @@ -0,0 +1 @@ +Use general Python expression syntax for introspection type hints \ No newline at end of file diff --git a/pyo3-introspection/src/introspection.rs b/pyo3-introspection/src/introspection.rs index 8bab0c4d2cd..27997f63518 100644 --- a/pyo3-introspection/src/introspection.rs +++ b/pyo3-introspection/src/introspection.rs @@ -428,7 +428,7 @@ fn deserialize_chunk( })?; serde_json::from_slice(chunk).with_context(|| { format!( - "Failed to parse introspection chunk: '{}'", + "Failed to parse introspection chunk: {:?}", String::from_utf8_lossy(chunk) ) }) diff --git a/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index 6924db0ad9d..f67cc88bca8 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -309,7 +309,10 @@ impl Imports { .chain(&elements_used_in_annotations.globals) { let normalized_module = if let Some(module) = module { - if possible_current_module_names.contains(module) { + if possible_current_module_names.contains(module) + || local_name_to_module_and_attribute.contains_key(module) + { + // This is importing a local element or from a local element, don't import None } else { Some(module.clone()) diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs index 733ed43d9bc..bb61c63a321 100644 --- a/src/inspect/mod.rs +++ b/src/inspect/mod.rs @@ -9,6 +9,7 @@ pub mod types; /// Builds a type hint from a module name and a member name in the module /// /// ``` +/// use pyo3::type_hint_identifier; /// use pyo3::inspect::PyStaticExpr; /// /// const T: PyStaticExpr = type_hint_identifier!("datetime", "date"); @@ -40,9 +41,10 @@ pub(crate) use type_hint_identifier; /// Builds the union of multiple type hints /// /// ``` +/// use pyo3::{type_hint_identifier, type_hint_union}; /// use pyo3::inspect::PyStaticExpr; /// -/// const T: PyStaticExpr = type_hint_union!(type_hint_identifier!("datetime.datetime"), type_hint_identifier!("datetime", "date")); +/// const T: PyStaticExpr = type_hint_union!(type_hint_identifier!("builtins", "int"), type_hint_identifier!("builtins", "float")); /// assert_eq!(T.to_string(), "int | float"); /// ``` #[macro_export] @@ -59,13 +61,14 @@ pub(crate) use type_hint_union; /// Builds a subscribed type hint /// /// ``` +/// use pyo3::{type_hint_identifier, type_hint_subscript}; /// use pyo3::inspect::PyStaticExpr; /// /// const T: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("collections.abc", "Sequence"), type_hint_identifier!("builtins", "float")); /// assert_eq!(T.to_string(), "collections.abc.Sequence[float]"); /// -/// const 2: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("builtins", "dict"), type_hint_identifier!("builtins", "str"), type_hint_identifier!("builtins", "float")); -/// assert_eq!(T.to_string(), "dict[str, float]"); +/// const T2: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("builtins", "dict"), type_hint_identifier!("builtins", "str"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T2.to_string(), "dict[str, float]"); /// ``` #[macro_export] macro_rules! type_hint_subscript { @@ -193,7 +196,7 @@ pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [ pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { match expr { PyStaticExpr::Constant { value } => match value { - PyStaticConstant::None => 34, + PyStaticConstant::None => 33, }, PyStaticExpr::Name { id, kind } => { (match kind { @@ -214,7 +217,7 @@ pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize { PyStaticExpr::Tuple { elts } => 5 + serialized_container_len_for_introspection(elts), PyStaticExpr::List { elts } => 4 + serialized_container_len_for_introspection(elts), PyStaticExpr::Subscript { value, slice } => { - 39 + serialized_len_for_introspection(value) + serialized_len_for_introspection(slice) + 38 + serialized_len_for_introspection(value) + serialized_len_for_introspection(slice) } } } @@ -335,7 +338,7 @@ const fn write_container_and_move_forward<'a>( } const fn serialized_container_len_for_introspection(elts: &[PyStaticExpr]) -> usize { - let mut len = 20; + let mut len = 21; let mut i = 0; while i < elts.len() { if i > 0 { @@ -385,36 +388,58 @@ mod tests { #[test] fn test_serialize_for_introspection() { - const T: PyStaticExpr = type_hint_subscript!( - type_hint_identifier!("typing", "Callable"), + fn check_serialization(expr: PyStaticExpr, expected: &str) { + let mut out = vec![0; serialized_len_for_introspection(&expr)]; + serialize_for_introspection(&expr, &mut out); + assert_eq!(std::str::from_utf8(&out).unwrap(), expected) + } + + check_serialization( + PyStaticExpr::Constant { + value: PyStaticConstant::None, + }, + r#"{"type":"constant","kind":"none"}"#, + ); + check_serialization( + type_hint_identifier!("builtins", "int"), + r#"{"type":"name","kind":"global","id":"int"}"#, + ); + check_serialization( + PyStaticExpr::Name { + id: "int", + kind: PyStaticNameKind::Local, + }, + r#"{"type":"name","kind":"local","id":"int"}"#, + ); + check_serialization( + type_hint_identifier!("datetime", "date"), + r#"{"type":"attribute","value":{"type":"name","kind":"global","id":"datetime"},"attr":"date"}"#, + ); + check_serialization( + type_hint_union!( + type_hint_identifier!("builtins", "int"), + type_hint_identifier!("builtins", "float") + ), + r#"{"type":"binop","left":{"type":"name","kind":"global","id":"int"},"op":"bitor","right":{"type":"name","kind":"global","id":"float"}}"#, + ); + check_serialization( + PyStaticExpr::Tuple { + elts: &[type_hint_identifier!("builtins", "list")], + }, + r#"{"type":"tuple","elts":[{"type":"name","kind":"global","id":"list"}]}"#, + ); + check_serialization( PyStaticExpr::List { - elts: &[ - type_hint_union!( - PyStaticExpr::Name { - id: "int", - kind: PyStaticNameKind::Global - }, - PyStaticExpr::Name { - id: "weird", - kind: PyStaticNameKind::Local - } - ), - type_hint_identifier!("datetime", "time"), - ] + elts: &[type_hint_identifier!("builtins", "list")], }, - PyStaticExpr::Constant { - value: PyStaticConstant::None - } + r#"{"type":"list","elts":[{"type":"name","kind":"global","id":"list"}]}"#, + ); + check_serialization( + type_hint_subscript!( + type_hint_identifier!("builtins", "list"), + type_hint_identifier!("builtins", "int") + ), + r#"{"type":"subscript","value":{"type":"name","kind":"global","id":"list"},"slice":{"type":"name","kind":"global","id":"int"}}"#, ); - const SER_LEN: usize = serialized_len_for_introspection(&T); - const SER: [u8; SER_LEN] = { - let mut out: [u8; SER_LEN] = [0; SER_LEN]; - serialize_for_introspection(&T, &mut out); - out - }; - assert_eq!( - std::str::from_utf8(&SER).unwrap(), - r#"{"type":"subscript","value":{"type":"attribute","value":{"type":"name","kind":"global","id":"typing"},"attr":"Callable"},"slice":{"type":"tuple","elts":[{"type":"list","elts":[{"type":"binop","left":{"type":"name","kind":"global","id":"int"},"op":"bitor","right":{"type":"name","kind":"local","id":"weird"}},{"type":"attribute","value":{"type":"name","kind":"global","id":"datetime"},"attr":"time"}]},{"type":"constant","kind":"none"}]}}"# - ) } } From da6a950a17e5e5078d13cb524a3f6ff19f5e87e7 Mon Sep 17 00:00:00 2001 From: Thomas Pellissier-Tanon Date: Fri, 12 Dec 2025 16:30:05 +0100 Subject: [PATCH 10/10] Nit --- pyo3-introspection/src/model.rs | 2 +- pyo3-macros-backend/src/pyclass.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pyo3-introspection/src/model.rs b/pyo3-introspection/src/model.rs index ef240fe44d1..e3828c02868 100644 --- a/pyo3-introspection/src/model.rs +++ b/pyo3-introspection/src/model.rs @@ -98,7 +98,7 @@ pub enum Expr { Tuple { elts: Vec }, /// A list List { elts: Vec }, - /// A subscript `main[*args]` + /// A subscript `value[slice]` Subscript { value: Box, slice: Box }, } diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a44b6f1600e..bfbba79e7ac 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1049,11 +1049,7 @@ fn impl_simple_enum( .doc(doc); let enum_into_pyobject_impl = { - let output_type = if cfg!(feature = "experimental-inspect") { - quote!(const OUTPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) - } else { - TokenStream::new() - }; + let output_type = get_conversion_type_hint(ctx, &format_ident!("OUTPUT_TYPE"), cls); let num = variants.len(); let i = (0..num).map(proc_macro2::Literal::usize_unsuffixed);