pyclass: move flags to PyClassImpl

This commit is contained in:
David Hewitt 2021-03-01 23:07:54 +00:00
parent e24f29dbf5
commit 31c7af2ac7
9 changed files with 67 additions and 85 deletions

View File

@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyMethodDef_INIT` [#1426](https://github.com/PyO3/pyo3/pull/1426)
- `PyTypeObject_INIT` [#1429](https://github.com/PyO3/pyo3/pull/1429)
- `PyObject_Check`, `PySuper_Check`, and `FreeFunc` [#1438](https://github.com/PyO3/pyo3/pull/1438)
- Remove pyclass implementation details `Type`, `DESCRIPTION`, and `FLAGS` from `PyTypeInfo`. [#1456](https://github.com/PyO3/pyo3/pull/1456)
### Fixed
- Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)

View File

@ -726,7 +726,6 @@ struct MyClass {
impl pyo3::pyclass::PyClassAlloc for MyClass {}
unsafe impl pyo3::PyTypeInfo for MyClass {
type Type = MyClass;
type BaseType = PyAny;
type BaseLayout = pyo3::pycell::PyCellBase<PyAny>;
type Layout = PyCell<Self>;
@ -735,8 +734,6 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
const NAME: &'static str = "MyClass";
const MODULE: Option<&'static str> = None;
const DESCRIPTION: &'static str = "Class for demonstration";
const FLAGS: usize = 0;
#[inline]
fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject {
@ -759,6 +756,10 @@ impl pyo3::IntoPy<PyObject> for MyClass {
}
impl pyo3::class::impl_::PyClassImpl for MyClass {
const DESCRIPTION: &'static str = "Class for demonstration";
const IS_GC: bool = false;
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub<MyClass>;
fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) {
@ -776,18 +777,18 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
}
fn get_new() -> Option<pyo3::ffi::newfunc> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<MyClass>::new();
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_call() -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<MyClass>::new();
let collector = PyClassImplCollector::<Self>::new();
collector.call_impl()
}
fn for_each_proto_slot(visitor: impl FnMut(&pyo3::ffi::PyType_Slot)) {
// Implementation which uses dtolnay specialization to load all slots.
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<MyClass>::new();
let collector = PyClassImplCollector::<Self>::new();
collector.object_protocol_slots()
.iter()
.chain(collector.number_protocol_slots())
@ -803,7 +804,7 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
fn get_buffer() -> Option<&'static pyo3::class::impl_::PyBufferProcs> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<MyClass>::new();
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
}

View File

@ -17,8 +17,11 @@ use syn::{parse_quote, spanned::Spanned, Expr, Token};
pub struct PyClassArgs {
pub freelist: Option<syn::Expr>,
pub name: Option<syn::Ident>,
pub flags: Vec<syn::Expr>,
pub base: syn::TypePath,
pub has_dict: bool,
pub has_weaklist: bool,
pub is_gc: bool,
pub is_basetype: bool,
pub has_extends: bool,
pub has_unsendable: bool,
pub module: Option<syn::LitStr>,
@ -42,10 +45,11 @@ impl Default for PyClassArgs {
freelist: None,
name: None,
module: None,
// We need the 0 as value for the constant we're later building using quote for when there
// are no other flags
flags: vec![parse_quote! { 0 }],
base: parse_quote! { pyo3::PyAny },
has_dict: false,
has_weaklist: false,
is_gc: false,
is_basetype: false,
has_extends: false,
has_unsendable: false,
}
@ -136,14 +140,19 @@ impl PyClassArgs {
/// Match a single flag
fn add_path(&mut self, exp: &syn::ExprPath) -> syn::Result<()> {
let flag = exp.path.segments.first().unwrap().ident.to_string();
let mut push_flag = |flag| {
self.flags.push(syn::Expr::Path(flag));
};
match flag.as_str() {
"gc" => push_flag(parse_quote! {pyo3::type_flags::GC}),
"weakref" => push_flag(parse_quote! {pyo3::type_flags::WEAKREF}),
"subclass" => push_flag(parse_quote! {pyo3::type_flags::BASETYPE}),
"dict" => push_flag(parse_quote! {pyo3::type_flags::DICT}),
"gc" => {
self.is_gc = true;
}
"weakref" => {
self.has_weaklist = true;
}
"subclass" => {
self.is_basetype = true;
}
"dict" => {
self.has_dict = true;
}
"unsendable" => {
self.has_unsendable = true;
}
@ -293,29 +302,14 @@ fn impl_class(
};
// insert space for weak ref
let mut has_weakref = false;
let mut has_dict = false;
let mut has_gc = false;
for f in attr.flags.iter() {
if let syn::Expr::Path(epath) = f {
if epath.path == parse_quote! { pyo3::type_flags::WEAKREF } {
has_weakref = true;
} else if epath.path == parse_quote! { pyo3::type_flags::DICT } {
has_dict = true;
} else if epath.path == parse_quote! { pyo3::type_flags::GC } {
has_gc = true;
}
}
}
let weakref = if has_weakref {
let weakref = if attr.has_weaklist {
quote! { pyo3::pyclass_slots::PyClassWeakRefSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as pyo3::derive_utils::PyBaseTypeUtils>::WeakRef }
} else {
quote! { pyo3::pyclass_slots::PyClassDummySlot }
};
let dict = if has_dict {
let dict = if attr.has_dict {
quote! { pyo3::pyclass_slots::PyClassDictSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as pyo3::derive_utils::PyBaseTypeUtils>::Dict }
@ -329,7 +323,7 @@ fn impl_class(
};
// Enforce at compile time that PyGCProtocol is implemented
let gc_impl = if has_gc {
let gc_impl = if attr.is_gc {
let closure_name = format!("__assertion_closure_{}", cls);
let closure_token = syn::Ident::new(&closure_name, Span::call_site());
quote! {
@ -357,12 +351,6 @@ fn impl_class(
};
let base = &attr.base;
let flags = &attr.flags;
let extended = if attr.has_extends {
quote! { pyo3::type_flags::EXTENDED }
} else {
quote! { 0 }
};
let base_layout = if attr.has_extends {
quote! { <Self::BaseType as pyo3::derive_utils::PyBaseTypeUtils>::LayoutAsBase }
} else {
@ -397,9 +385,12 @@ fn impl_class(
quote! { pyo3::class::impl_::ThreadCheckerStub<#cls> }
};
let is_gc = attr.is_gc;
let is_basetype = attr.is_basetype;
let is_subclass = attr.has_extends;
Ok(quote! {
unsafe impl pyo3::type_object::PyTypeInfo for #cls {
type Type = #cls;
type BaseType = #base;
type Layout = pyo3::PyCell<Self>;
type BaseLayout = #base_layout;
@ -408,8 +399,6 @@ fn impl_class(
const NAME: &'static str = #cls_name;
const MODULE: Option<&'static str> = #module;
const DESCRIPTION: &'static str = #doc;
const FLAGS: usize = #(#flags)|* | #extended;
#[inline]
fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject {
@ -440,6 +429,11 @@ fn impl_class(
#impl_inventory
impl pyo3::class::impl_::PyClassImpl for #cls {
const DESCRIPTION: &'static str = #doc;
const IS_GC: bool = #is_gc;
const IS_BASETYPE: bool = #is_basetype;
const IS_SUBCLASS: bool = #is_subclass;
type ThreadChecker = #thread_checker;
fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) {

View File

@ -1,6 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::{derive_utils::PyBaseTypeUtils, ffi, PyMethodDefType, PyNativeType};
use crate::{derive_utils::PyBaseTypeUtils, ffi, PyMethodDefType, PyNativeType, PyTypeInfo};
use std::{marker::PhantomData, thread};
/// This type is used as a "dummy" type on which dtolnay specializations are
@ -31,7 +31,19 @@ impl<T> Copy for PyClassImplCollector<T> {}
///
/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
/// and may be changed at any time.
pub trait PyClassImpl: Sized {
pub trait PyClassImpl: Sized + PyTypeInfo {
/// Class doc string
const DESCRIPTION: &'static str = "\0";
/// #[pyclass(gc)]
const IS_GC: bool = false;
/// #[pyclass(subclass)]
const IS_BASETYPE: bool = false;
/// #[pyclass(extends=...)]
const IS_SUBCLASS: bool = false;
/// This handles following two situations:
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
/// This implementation is used by default. Compile fails if `T: !Send`.

View File

@ -2,8 +2,9 @@
//! Free allocation list
use crate::class::impl_::PyClassImpl;
use crate::pyclass::{get_type_free, tp_free_fallback, PyClassAlloc};
use crate::type_object::{PyLayout, PyTypeInfo};
use crate::type_object::PyLayout;
use crate::{ffi, AsPyPointer, FromPyPointer, PyAny, Python};
use std::mem;
use std::os::raw::c_void;
@ -69,7 +70,7 @@ impl<T> FreeList<T> {
impl<T> PyClassAlloc for T
where
T: PyTypeInfo + PyClassWithFreeList,
T: PyClassImpl + PyClassWithFreeList,
{
unsafe fn new(py: Python, subtype: *mut ffi::PyTypeObject) -> *mut Self::Layout {
// if subtype is not equal to this type, we cannot use the freelist

View File

@ -159,7 +159,7 @@ pub use crate::pycell::{PyCell, PyRef, PyRefMut};
pub use crate::pyclass::PyClass;
pub use crate::pyclass_init::PyClassInitializer;
pub use crate::python::{Python, PythonVersionInfo};
pub use crate::type_object::{type_flags, PyTypeInfo};
pub use crate::type_object::PyTypeInfo;
// Since PyAny is as important as PyObject, we expose it to the top level.
pub use crate::types::PyAny;

View File

@ -2,7 +2,7 @@
use crate::class::impl_::PyClassImpl;
use crate::class::methods::PyMethodDefType;
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
use crate::type_object::{type_flags, PyLayout};
use crate::type_object::PyLayout;
use crate::{ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python};
use std::convert::TryInto;
use std::ffi::CString;
@ -40,12 +40,12 @@ pub(crate) unsafe fn bpo_35810_workaround(_py: Python, ty: *mut ffi::PyTypeObjec
}
#[inline]
pub(crate) unsafe fn default_new<T: PyTypeInfo>(
pub(crate) unsafe fn default_new<T: PyClassImpl>(
py: Python,
subtype: *mut ffi::PyTypeObject,
) -> *mut ffi::PyObject {
// if the class derives native types(e.g., PyDict), call special new
if T::FLAGS & type_flags::EXTENDED != 0 && T::BaseLayout::IS_NATIVE_TYPE {
if T::IS_SUBCLASS && T::BaseLayout::IS_NATIVE_TYPE {
#[cfg(not(Py_LIMITED_API))]
{
let base_tp = T::BaseType::type_object_raw(py);
@ -70,7 +70,7 @@ pub(crate) unsafe fn default_new<T: PyTypeInfo>(
}
/// This trait enables custom `tp_new`/`tp_dealloc` implementations for `T: PyClass`.
pub trait PyClassAlloc: PyTypeInfo + Sized {
pub trait PyClassAlloc: PyClassImpl {
/// Allocate the actual field for `#[pyclass]`.
///
/// # Safety
@ -153,6 +153,7 @@ pub trait PyClass:
struct TypeSlots(Vec<ffi::PyType_Slot>);
impl TypeSlots {
#[inline]
fn push(&mut self, slot: c_int, pfunc: *mut c_void) {
self.0.push(ffi::PyType_Slot { slot, pfunc });
}
@ -174,6 +175,7 @@ fn get_type_name<T: PyTypeInfo>(module_name: Option<&str>) -> PyResult<*mut c_ch
})
}
#[inline]
fn into_raw<T>(vec: Vec<T>) -> *mut c_void {
Box::into_raw(vec.into_boxed_slice()) as _
}
@ -300,12 +302,12 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
fn tp_init_additional<T: PyClass>(_type_object: *mut ffi::PyTypeObject) {}
fn py_class_flags<T: PyClass + PyTypeInfo>(has_gc_methods: bool) -> c_uint {
let mut flags = if has_gc_methods || T::FLAGS & type_flags::GC != 0 {
let mut flags = if has_gc_methods || T::IS_GC {
ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC
} else {
ffi::Py_TPFLAGS_DEFAULT
};
if T::FLAGS & type_flags::BASETYPE != 0 {
if T::IS_BASETYPE {
flags |= ffi::Py_TPFLAGS_BASETYPE;
}
flags.try_into().unwrap()

View File

@ -46,25 +46,6 @@ pub trait PySizedLayout<T: PyTypeInfo>: PyLayout<T> + Sized {}
/// Otherwise, implementing this trait is undefined behavior.
pub unsafe trait PyBorrowFlagLayout<T: PyTypeInfo>: PyLayout<T> + Sized {}
/// Our custom type flags
#[doc(hidden)]
pub mod type_flags {
/// Type object supports Python GC
pub const GC: usize = 1;
/// Type object supports Python weak references
pub const WEAKREF: usize = 1 << 1;
/// Type object can be used as the base type of another type
pub const BASETYPE: usize = 1 << 2;
/// The instances of this type have a dictionary containing instance variables
pub const DICT: usize = 1 << 3;
/// The class declared by #[pyclass(extends=~)]
pub const EXTENDED: usize = 1 << 4;
}
/// Python type information.
/// All Python native types(e.g., `PyDict`) and `#[pyclass]` structs implement this trait.
///
@ -72,21 +53,12 @@ pub mod type_flags {
/// - specifying the incorrect layout can lead to memory errors
/// - the return value of type_object must always point to the same PyTypeObject instance
pub unsafe trait PyTypeInfo: Sized {
/// Type of objects to store in PyObject struct
type Type;
/// Class name
const NAME: &'static str;
/// Module name, if any
const MODULE: Option<&'static str>;
/// Class doc string
const DESCRIPTION: &'static str = "\0";
/// Type flags (ie PY_TYPE_FLAG_GC, PY_TYPE_FLAG_WEAKREF)
const FLAGS: usize = 0;
/// Base class
type BaseType: PyTypeInfo + PyTypeObject;

View File

@ -197,7 +197,6 @@ macro_rules! pyobject_native_type_info(
($name: ty, $layout: path, $typeobject: expr,
$module: expr $(, $checkfunction:path)? $(;$generics: ident)*) => {
unsafe impl<$($generics,)*> $crate::type_object::PyTypeInfo for $name {
type Type = ();
type BaseType = $crate::PyAny;
type Layout = $layout;
type BaseLayout = $crate::ffi::PyObject;