Skip to content

Commit b6e16e8

Browse files
committed
Implements a proc macro for exception introspection
1 parent 472002c commit b6e16e8

File tree

7 files changed

+67
-52
lines changed

7 files changed

+67
-52
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ uuid = { version = "1.11.0", optional = true }
6161
lock_api = { version = "0.4", optional = true }
6262
parking_lot = { version = "0.12", optional = true }
6363
iana-time-zone = { version = "0.1", optional = true, features = ["fallback"] }
64-
paste = { version = "1", optional = true }
6564

6665
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
6766
portable-atomic = "1.0"
@@ -94,7 +93,7 @@ experimental-async = ["macros", "pyo3-macros/experimental-async"]
9493

9594
# Enables pyo3::inspect module and additional type information on FromPyObject
9695
# and IntoPy traits
97-
experimental-inspect = ["pyo3-macros/experimental-inspect", "paste"]
96+
experimental-inspect = ["pyo3-macros/experimental-inspect"]
9897

9998
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
10099
macros = ["pyo3-macros"]

pyo3-macros-backend/src/introspection.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,48 @@ use std::collections::HashMap;
2020
use std::hash::{Hash, Hasher};
2121
use std::mem::take;
2222
use std::sync::atomic::{AtomicUsize, Ordering};
23-
use syn::{Attribute, Ident, ReturnType, Type, TypePath};
23+
use syn::ext::IdentExt;
24+
use syn::parse::{Parse, ParseStream};
25+
use syn::{Attribute, Ident, Path, ReturnType, Token, Type, TypePath};
2426

2527
static GLOBAL_COUNTER_FOR_UNIQUE_NAMES: AtomicUsize = AtomicUsize::new(0);
2628

29+
/// Entry point to implement introspection on exceptions
30+
pub fn implement_class_introspection(options: ClassIntrospectionOptions) -> TokenStream {
31+
class_introspection_code(
32+
&PyO3CratePath::Given(options.pyo3_class_path),
33+
&options.name,
34+
&options.name.unraw().to_string(),
35+
options.base.map(|t| PythonTypeHint::from_type(t, None)),
36+
false,
37+
)
38+
}
39+
40+
pub struct ClassIntrospectionOptions {
41+
pub pyo3_class_path: Path,
42+
pub name: Ident,
43+
pub base: Option<Type>,
44+
}
45+
46+
impl Parse for ClassIntrospectionOptions {
47+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
48+
let pyo3_class_path = input.parse()?;
49+
let _: Token![,] = input.parse()?;
50+
let name = input.parse()?;
51+
let base = if input.peek(Token![,]) {
52+
let _: Token![,] = input.parse()?;
53+
Some(input.parse()?)
54+
} else {
55+
None
56+
};
57+
Ok(Self {
58+
pyo3_class_path,
59+
name,
60+
base,
61+
})
62+
}
63+
}
64+
2765
pub fn module_introspection_code<'a>(
2866
pyo3_crate_path: &PyO3CratePath,
2967
name: &str,

pyo3-macros-backend/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ mod type_hint;
3030

3131
pub use frompyobject::build_derive_from_pyobject;
3232
pub use intopyobject::build_derive_into_pyobject;
33+
#[cfg(feature = "experimental-inspect")]
34+
pub use introspection::{implement_class_introspection, ClassIntrospectionOptions};
3335
pub use module::{pymodule_function_impl, pymodule_module_impl, PyModuleOptions};
3436
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
3537
pub use pyfunction::{build_py_function, PyFunctionOptions};

pyo3-macros/src/lib.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -159,32 +159,25 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
159159
#[proc_macro_derive(IntoPyObject, attributes(pyo3))]
160160
pub fn derive_into_py_object(item: TokenStream) -> TokenStream {
161161
let ast = parse_macro_input!(item as syn::DeriveInput);
162-
let expanded = build_derive_into_pyobject::<false>(&ast).unwrap_or_compile_error();
163-
quote!(
164-
#expanded
165-
)
166-
.into()
162+
build_derive_into_pyobject::<false>(&ast)
163+
.unwrap_or_compile_error()
164+
.into()
167165
}
168166

169167
#[proc_macro_derive(IntoPyObjectRef, attributes(pyo3))]
170168
pub fn derive_into_py_object_ref(item: TokenStream) -> TokenStream {
171169
let ast = parse_macro_input!(item as syn::DeriveInput);
172-
let expanded =
173-
pyo3_macros_backend::build_derive_into_pyobject::<true>(&ast).unwrap_or_compile_error();
174-
quote!(
175-
#expanded
176-
)
177-
.into()
170+
build_derive_into_pyobject::<true>(&ast)
171+
.unwrap_or_compile_error()
172+
.into()
178173
}
179174

180175
#[proc_macro_derive(FromPyObject, attributes(pyo3))]
181176
pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
182177
let ast = parse_macro_input!(item as syn::DeriveInput);
183-
let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error();
184-
quote!(
185-
#expanded
186-
)
187-
.into()
178+
build_derive_from_pyobject(&ast)
179+
.unwrap_or_compile_error()
180+
.into()
188181
}
189182

190183
fn pyclass_impl(
@@ -254,3 +247,11 @@ impl UnwrapOrCompileError for syn::Result<TokenStream2> {
254247
self.unwrap_or_else(|e| e.into_compile_error())
255248
}
256249
}
250+
251+
#[cfg(feature = "experimental-inspect")]
252+
#[doc(hidden)]
253+
#[proc_macro]
254+
pub fn implement_class_introspection(args: TokenStream) -> TokenStream {
255+
let options = parse_macro_input!(args as pyo3_macros_backend::ClassIntrospectionOptions);
256+
pyo3_macros_backend::implement_class_introspection(options).into()
257+
}

src/exceptions.rs

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ macro_rules! create_exception_type_object {
231231
}
232232
}
233233

234-
$crate::create_exception_introspection_data!($module, $name, $base);
234+
$crate::create_exception_introspection_data!($name, $base);
235235
};
236236
}
237237

@@ -240,42 +240,15 @@ macro_rules! create_exception_type_object {
240240
#[doc(hidden)]
241241
#[macro_export]
242242
macro_rules! create_exception_introspection_data(
243-
($module: expr, $name: ident, $base: ty) => {};
243+
($name: ident, $base: ty) => {};
244244
);
245245

246-
#[cfg(feature = "experimental-inspect")]
246+
#[cfg(all(feature = "experimental-inspect", feature = "macros"))]
247247
#[doc(hidden)]
248248
#[macro_export]
249249
macro_rules! create_exception_introspection_data(
250-
($module: expr, $name: ident, $base: ty) => {
251-
const _: () = {
252-
const PIECES: &[&[u8]] = &[
253-
b"{\"type\":\"class\",\"id\":\"",
254-
$name::_PYO3_INTROSPECTION_ID.as_bytes(),
255-
b"\",\"name\":\"",
256-
stringify!($name).as_bytes(),
257-
b"\",\"bases\":[",
258-
{
259-
const BASE_LEN: usize = $crate::inspect::serialized_len_for_introspection(&<$base as $crate::type_object::PyTypeInfo>::TYPE_HINT);
260-
const BASE_SER: [u8; BASE_LEN] = {
261-
let mut result: [u8; BASE_LEN] = [0; BASE_LEN];
262-
$crate::inspect::serialize_for_introspection(&<$base as $crate::type_object::PyTypeInfo>::TYPE_HINT, &mut result);
263-
result
264-
};
265-
&BASE_SER
266-
},
267-
b"]}"
268-
];
269-
const PIECES_LEN: usize = $crate::impl_::concat::combined_len(PIECES);
270-
$crate::impl_::introspection::paste! {
271-
#[used]
272-
#[no_mangle]
273-
static [<PYO3_INTROSPECTION_1_ $name>]: $crate::impl_::introspection::SerializedIntrospectionFragment<PIECES_LEN> = $crate::impl_::introspection::SerializedIntrospectionFragment {
274-
length: PIECES_LEN as u32,
275-
fragment: $crate::impl_::concat::combine_to_array::<PIECES_LEN>(PIECES)
276-
};
277-
}
278-
};
250+
($name: ident, $base: ty) => {
251+
$crate::implement_class_introspection!($crate, $name, $base);
279252
};
280253
);
281254

src/impl_/introspection.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::conversion::IntoPyObject;
22
use crate::inspect::TypeHint;
3-
pub use paste::paste;
43

54
/// Trait to guess a function Python return type
65
///

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ mod version;
445445
)]
446446
pub use crate::conversions::*;
447447

448+
#[cfg(all(feature = "experimental-inspect", feature = "macros"))]
449+
#[doc(hidden)]
450+
pub use pyo3_macros::implement_class_introspection;
448451
#[cfg(feature = "macros")]
449452
pub use pyo3_macros::{
450453
pyfunction, pymethods, pymodule, FromPyObject, IntoPyObject, IntoPyObjectRef,

0 commit comments

Comments
 (0)