From 59ed277879cf203c690de4dd5f731f0cb110636e Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 14 Jul 2019 16:35:06 +0200 Subject: [PATCH] Enforce GC contract at compile time PyGCProtocol must be implemented for anything registered as tracked by the garbage collector. This modifies the `pyclass` macro to enforce this at compile time. --- pyo3-derive-backend/src/pyclass.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 0d9a334a..5e7841e6 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -296,12 +296,15 @@ 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(ref epath) = f { if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} { has_weakref = true; } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} { has_dict = true; + } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} { + has_gc = true; } } } @@ -321,6 +324,22 @@ fn impl_class( quote! { None } }; + // Enforce at compile time that PyGCProtocol is implemented + let gc_impl = if has_gc { + let closure_name = format!("__assertion_closure_{}", cls.to_string()); + let closure_token = syn::Ident::new(&closure_name, Span::call_site()); + quote! { + fn #closure_token() { + use pyo3::class; + + fn _assert_implements_protocol<'p, T: pyo3::class::PyGCProtocol<'p>>() {} + _assert_implements_protocol::<#cls>(); + } + } + } else { + quote! {} + }; + let inventory_impl = impl_inventory(&cls); let base = &attr.base; @@ -365,6 +384,9 @@ fn impl_class( #inventory_impl #extra + + #gc_impl + } }