Skip to content

Commit dc7d940

Browse files
committed
Introspection: properly set OUTPUT_TYPE on IntoPyObject implementations for Bound and Borrow and add a PyTypeCheck to them
We need the PyTypeCheck bound to get the type hint for the underlying type
1 parent e4f90c6 commit dc7d940

File tree

5 files changed

+35
-19
lines changed

5 files changed

+35
-19
lines changed

guide/src/type-stub.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,5 @@ This is useful when PyO3 is not able to derive proper type annotations by itself
7979
- Introspection only works with Python modules declared with an inline Rust module.
8080
Modules declared using a function are not supported.
8181
- `FromPyObject::INPUT_TYPE` and `IntoPyObject::OUTPUT_TYPE` must be implemented for PyO3 to get the proper input/output type annotations to use.
82-
- Because `FromPyObject::INPUT_TYPE` and `IntoPyObject::OUTPUT_TYPE` are `const` it is not possible to build yet smart generic annotations for containers like `concat!("list[", T::OUTPUT_TYPE, "]")`.
83-
See [this tracking issue](https://github.com/rust-lang/rust/issues/76560).
8482
- PyO3 is not able to introspect the content of `#[pymodule]` and `#[pymodule_init]` functions.
8583
If they are present, the module is tagged as incomplete using a fake `def __getattr__(name: str) -> Incomplete: ...` function [following best practices](https://typing.python.org/en/latest/guides/writing_stubs.html#incomplete-stubs).

newsfragments/5640.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
IntoPyObject implementation on Bound, Borrowed and Py: add a PyTypeCheck bound to emit typing information

pytests/stubs/pyfunctions.pyi

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
from typing import Any
2-
3-
def args_kwargs(*args, **kwargs) -> tuple[Any, Any | None]: ...
1+
def args_kwargs(*args, **kwargs) -> tuple[tuple, dict | None]: ...
42
def many_keyword_arguments(
53
*,
64
ant: object | None = None,
@@ -21,19 +19,19 @@ def many_keyword_arguments(
2119
penguin: object | None = None,
2220
) -> None: ...
2321
def none() -> None: ...
24-
def positional_only(a: object, /, b: object) -> tuple[Any, Any]: ...
22+
def positional_only(a: object, /, b: object) -> tuple[object, object]: ...
2523
def simple(
2624
a: object, b: object | None = None, *, c: object | None = None
27-
) -> tuple[Any, Any | None, Any | None]: ...
25+
) -> tuple[object, object | None, object | None]: ...
2826
def simple_args(
2927
a: object, b: object | None = None, *args, c: object | None = None
30-
) -> tuple[Any, Any | None, Any, Any | None]: ...
28+
) -> tuple[object, object | None, tuple, object | None]: ...
3129
def simple_args_kwargs(
3230
a: object, b: object | None = None, *args, c: object | None = None, **kwargs
33-
) -> tuple[Any, Any | None, Any, Any | None, Any | None]: ...
31+
) -> tuple[object, object | None, tuple, object | None, dict | None]: ...
3432
def simple_kwargs(
3533
a: object, b: object | None = None, c: object | None = None, **kwargs
36-
) -> tuple[Any, Any | None, Any | None, Any | None]: ...
34+
) -> tuple[object, object | None, object | None, dict | None]: ...
3735
def with_custom_type_annotations(
3836
a: int, *_args: str, _b: int | None = None, **_kwargs: bool
3937
) -> int: ...

src/conversion.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::pyclass::boolean_struct::False;
99
use crate::pyclass::{PyClassGuardError, PyClassGuardMutError};
1010
use crate::types::PyTuple;
1111
use crate::{
12-
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut, Python,
12+
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut,
13+
PyTypeCheck, Python,
1314
};
1415
use std::convert::Infallible;
1516
use std::marker::PhantomData;
@@ -123,61 +124,79 @@ pub(crate) mod private {
123124
}
124125
}
125126

126-
impl<'py, T> IntoPyObject<'py> for Bound<'py, T> {
127+
impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'py, T> {
127128
type Target = T;
128129
type Output = Bound<'py, Self::Target>;
129130
type Error = Infallible;
130131

132+
#[cfg(feature = "experimental-inspect")]
133+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
134+
131135
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
132136
Ok(self)
133137
}
134138
}
135139

136-
impl<'a, 'py, T> IntoPyObject<'py> for &'a Bound<'py, T> {
140+
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'py, T> {
137141
type Target = T;
138142
type Output = Borrowed<'a, 'py, Self::Target>;
139143
type Error = Infallible;
140144

145+
#[cfg(feature = "experimental-inspect")]
146+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
147+
141148
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
142149
Ok(self.as_borrowed())
143150
}
144151
}
145152

146-
impl<'a, 'py, T> IntoPyObject<'py> for Borrowed<'a, 'py, T> {
153+
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, 'py, T> {
147154
type Target = T;
148155
type Output = Borrowed<'a, 'py, Self::Target>;
149156
type Error = Infallible;
150157

158+
#[cfg(feature = "experimental-inspect")]
159+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
160+
151161
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
152162
Ok(self)
153163
}
154164
}
155165

156-
impl<'a, 'py, T> IntoPyObject<'py> for &Borrowed<'a, 'py, T> {
166+
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, 'py, T> {
157167
type Target = T;
158168
type Output = Borrowed<'a, 'py, Self::Target>;
159169
type Error = Infallible;
160170

171+
#[cfg(feature = "experimental-inspect")]
172+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
173+
161174
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
162175
Ok(*self)
163176
}
164177
}
165178

166-
impl<'py, T> IntoPyObject<'py> for Py<T> {
179+
impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Py<T> {
167180
type Target = T;
168181
type Output = Bound<'py, Self::Target>;
169182
type Error = Infallible;
170183

184+
#[cfg(feature = "experimental-inspect")]
185+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
186+
171187
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
172188
Ok(self.into_bound(py))
173189
}
174190
}
175191

176-
impl<'a, 'py, T> IntoPyObject<'py> for &'a Py<T> {
192+
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Py<T> {
177193
type Target = T;
178194
type Output = Borrowed<'a, 'py, Self::Target>;
179195
type Error = Infallible;
180196

197+
#[cfg(feature = "experimental-inspect")]
198+
const OUTPUT_TYPE: TypeHint = T::TYPE_HINT;
199+
181200
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
182201
Ok(self.bind_borrowed(py))
183202
}

src/impl_/pyclass.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
pycell::PyBorrowError,
1212
types::{any::PyAnyMethods, PyBool},
1313
Borrowed, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyClassGuard, PyErr, PyResult,
14-
PyTypeInfo, Python,
14+
PyTypeCheck, PyTypeInfo, Python,
1515
};
1616
use std::{
1717
ffi::CStr,
@@ -1204,7 +1204,7 @@ impl<
12041204

12051205
impl<
12061206
ClassT: PyClass,
1207-
U,
1207+
U: PyTypeCheck,
12081208
const OFFSET: usize,
12091209
const IMPLEMENTS_INTOPYOBJECT_REF: bool,
12101210
const IMPLEMENTS_INTOPYOBJECT: bool,

0 commit comments

Comments
 (0)