use crate::conversion::IntoPyObject;
use crate::impl_::pyclass::PyClassImpl;
use crate::inspect::PyStaticExpr;
use crate::pycell::impl_::PyClassObjectLayout;
mod return_type {
use crate::{IntoPyObject, impl_::introspection::PyReturnType};
pub trait Sealed {}
impl<'a, T: IntoPyObject<'a>> Sealed for T {}
impl<T: PyReturnType, E> Sealed for Result<T, E> {}
}
pub trait PyReturnType: return_type::Sealed {
const OUTPUT_TYPE: PyStaticExpr;
}
impl<'a, T: IntoPyObject<'a>> PyReturnType for T {
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
}
impl<T: PyReturnType, E> PyReturnType for Result<T, E> {
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be converted to a Python object",
label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
)]
pub trait PyIntoPyObjectMaybeRefType<const REF_IMPL_EXISTS: bool> {
const OUTPUT_TYPE: PyStaticExpr;
}
impl<'a, 'py, T: 'a> PyIntoPyObjectMaybeRefType<true> for T
where
&'a T: IntoPyObject<'py>,
{
const OUTPUT_TYPE: PyStaticExpr = <&T as IntoPyObject<'_>>::OUTPUT_TYPE;
}
impl<'py, T: IntoPyObject<'py>> PyIntoPyObjectMaybeRefType<false> for T {
const OUTPUT_TYPE: PyStaticExpr = <T as IntoPyObject<'_>>::OUTPUT_TYPE;
}
pub const fn is_disjoint_base<T: PyClassImpl>() -> bool {
<T::Layout as PyClassObjectLayout<T>>::IS_DISJOINT_BASE
}
pub const fn typing_or_extensions_if_not_3_15() -> PyStaticExpr {
PyStaticExpr::Name {
id: if cfg!(Py_3_15) {
"typing"
} else {
"typing_extensions"
},
}
}
#[repr(C)]
pub struct SerializedIntrospectionFragment<const LEN: usize> {
pub length: u32,
pub fragment: [u8; LEN],
}
pub const fn escape_json_string(input: &str, output: &mut [u8]) -> usize {
let input = input.as_bytes();
let mut input_i = 0;
let mut output_i = 0;
while input_i < input.len() {
match input[input_i] {
b'\\' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'\\';
output_i += 1;
}
b'"' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'"';
output_i += 1;
}
0x08 => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'b';
output_i += 1;
}
0x0C => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'f';
output_i += 1;
}
b'\n' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'n';
output_i += 1;
}
b'\r' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'r';
output_i += 1;
}
b'\t' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b't';
output_i += 1;
}
c @ 0..32 => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'u';
output_i += 1;
output[output_i] = b'0';
output_i += 1;
output[output_i] = b'0';
output_i += 1;
output[output_i] = b'0' + (c / 16);
output_i += 1;
let remainder = c % 16;
output[output_i] = if remainder >= 10 {
b'a' + remainder - 10
} else {
b'0' + remainder
};
output_i += 1;
}
c => {
output[output_i] = c;
output_i += 1;
}
}
input_i += 1;
}
output_i
}
pub const fn escaped_json_string_len(input: &str) -> usize {
let input = input.as_bytes();
let mut len = 0;
let mut i = 0;
while i < input.len() {
len += match input[i] {
b'\\' | b'"' | 0x08 | 0x0C | b'\n' | b'\r' | b'\t' => 2,
0..32 => 6,
_ => 1,
};
i += 1;
}
len
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use super::*;
use crate::PyTypeInfo;
use crate::prelude::*;
#[pyclass(crate = "crate", frozen, subclass)]
struct Empty;
#[pyclass(crate = "crate", frozen, subclass)]
struct WithField(#[allow(dead_code)] u64);
#[pyclass(crate = "crate", subclass)]
struct Mutable;
#[pyclass(crate = "crate", extends = Empty, frozen)]
struct EmptyChild;
#[pyclass(crate = "crate", subclass, frozen)]
struct SizedBase(#[allow(dead_code)] u64);
#[pyclass(crate = "crate", extends = SizedBase, frozen)]
#[repr(align(128))]
struct OverAligned;
fn runtime_is_disjoint_base<T: PyTypeInfo>(py: Python<'_>) -> bool {
let attr =
|obj: &Bound<'_, PyAny>, name| obj.getattr(name).unwrap().extract::<isize>().unwrap();
let ty = T::type_object(py).into_any();
let base = ty.getattr("__base__").unwrap();
attr(&ty, "__basicsize__") != attr(&base, "__basicsize__")
|| attr(&ty, "__itemsize__") != attr(&base, "__itemsize__")
}
#[test]
fn is_disjoint_base_matches_runtime_layout() {
Python::attach(|py| {
assert_eq!(
is_disjoint_base::<Empty>(),
runtime_is_disjoint_base::<Empty>(py)
);
assert_eq!(
is_disjoint_base::<WithField>(),
runtime_is_disjoint_base::<WithField>(py)
);
assert_eq!(
is_disjoint_base::<Mutable>(),
runtime_is_disjoint_base::<Mutable>(py)
);
assert_eq!(
is_disjoint_base::<EmptyChild>(),
runtime_is_disjoint_base::<EmptyChild>(py)
);
assert_eq!(
is_disjoint_base::<SizedBase>(),
runtime_is_disjoint_base::<SizedBase>(py)
);
assert_eq!(
core::mem::size_of::<crate::pycell::impl_::PyClassObjectContents<OverAligned>>(),
0
);
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
assert!(runtime_is_disjoint_base::<OverAligned>(py));
assert_eq!(
is_disjoint_base::<OverAligned>(),
runtime_is_disjoint_base::<OverAligned>(py)
);
assert!(is_disjoint_base::<WithField>());
assert!(!is_disjoint_base::<EmptyChild>());
});
}
#[test]
fn typing_is_only_chosen_when_it_has_disjoint_base() {
Python::attach(|py| {
let PyStaticExpr::Name { id } = typing_or_extensions_if_not_3_15() else {
panic!("expected a module name");
};
let typing_has_it = py
.import("typing")
.unwrap()
.hasattr("disjoint_base")
.unwrap();
assert!(id == "typing_extensions" || typing_has_it);
});
}
}