diff --git a/Cargo.toml b/Cargo.toml index 721328c..112be48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,33 +14,30 @@ edition.workspace = true members = ["derive"] [workspace.package] -version = "0.2.0" +version = "0.2.0" # Also update in [dependencies.rust-cc-derive.version] authors = ["fren_gor "] repository = "https://github.com/frengor/rust-cc" categories = ["memory-management"] license = "MIT OR Apache-2.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [features] -default = ["auto-collect", "finalization"] -nightly = [] +default = ["auto-collect", "finalization", "derive"] +nightly = ["rust-cc-derive?/nightly"] +derive = ["dep:rust-cc-derive"] auto-collect = [] finalization = [] pedantic-debug-assertions = [] [dependencies] +rust-cc-derive = { path = "./derive", version = "0.2.0", optional = true } thiserror = "1.0.37" [dev-dependencies] -iai-callgrind = "0.7.3" +iai-callgrind = "0.7.3" # Also update IAI_CALLGRIND_VERSION in .github/workflows/bench.yml rand = "0.8.3" +trybuild = "1.0.85" [[bench]] name = "bench" harness = false - -[package.metadata.cargo-all-features] -# Exclude certain features from the build matrix -denylist = ["full"] diff --git a/benches/benches/binary_trees.rs b/benches/benches/binary_trees.rs index b04d7d8..c4b284a 100644 --- a/benches/benches/binary_trees.rs +++ b/benches/benches/binary_trees.rs @@ -24,6 +24,7 @@ pub fn count_binary_trees(max_size: usize) -> Vec { res } +#[derive(Trace, Finalize)] enum TreeNode { Nested { left: Cc, @@ -32,17 +33,6 @@ enum TreeNode { End, } -unsafe impl Trace for TreeNode { - fn trace(&self, ctx: &mut Context<'_>) { - if let Self::Nested { left, right } = self { - left.trace(ctx); - right.trace(ctx); - } - } -} - -impl Finalize for TreeNode {} - impl TreeNode { fn new(depth: usize) -> Self { if depth == 0 { diff --git a/benches/benches/binary_trees_with_parent_pointers.rs b/benches/benches/binary_trees_with_parent_pointers.rs index c30b7df..41f5c6a 100644 --- a/benches/benches/binary_trees_with_parent_pointers.rs +++ b/benches/benches/binary_trees_with_parent_pointers.rs @@ -26,6 +26,7 @@ pub fn count_binary_trees_with_parent(max_size: usize) -> Vec { res } +#[derive(Trace, Finalize)] enum TreeNodeWithParent { Root { left: Cc, @@ -39,25 +40,6 @@ enum TreeNodeWithParent { End, } -unsafe impl Trace for TreeNodeWithParent { - fn trace(&self, ctx: &mut Context<'_>) { - match self { - Self::Root { left, right } => { - left.trace(ctx); - right.trace(ctx); - } - Self::Nested { parent, left, right } => { - parent.trace(ctx); - left.trace(ctx); - right.trace(ctx); - } - Self::End => {}, - } - } -} - -impl Finalize for TreeNodeWithParent {} - impl TreeNodeWithParent { fn new(depth: usize) -> Cc { if depth == 0 { diff --git a/benches/benches/large_linked_list.rs b/benches/benches/large_linked_list.rs index 8fa2fd2..22ac092 100644 --- a/benches/benches/large_linked_list.rs +++ b/benches/benches/large_linked_list.rs @@ -42,6 +42,7 @@ impl List { } } +#[derive(Trace, Finalize)] enum Node { Cons { next: Cc, previous: RefCell>> }, Nil, @@ -57,17 +58,3 @@ impl Node { } } } - -unsafe impl Trace for Node { - fn trace(&self, ctx: &mut Context<'_>) { - match self { - Self::Cons { next, previous } => { - next.trace(ctx); - previous.trace(ctx); - }, - Self::Nil => {}, - } - } -} - -impl Finalize for Node {} diff --git a/benches/benches/stress_test.rs b/benches/benches/stress_test.rs index 3860c6d..de776f4 100644 --- a/benches/benches/stress_test.rs +++ b/benches/benches/stress_test.rs @@ -9,19 +9,12 @@ use rust_cc::*; // BENCHMARK 1: My janky stress test // (It basically creates a graph where every node is rooted, then de-roots some nodes a few at a time) +#[derive(Trace, Finalize)] struct DirectedGraphNode { _label: String, edges: Vec>>, } -unsafe impl Trace for DirectedGraphNode { - fn trace(&self, ctx: &mut Context<'_>) { - self.edges.iter().for_each(|elem| elem.trace(ctx)); - } -} - -impl Finalize for DirectedGraphNode {} - const NODE_COUNT: usize = 1 << 15; const EDGE_COUNT: usize = 1 << 15; const SHRINK_DIV: usize = 1 << 10; diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 0294f02..ed81eb6 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rust-cc-derive" description = "Derive macro for rust-cc" -version = "0.0.0" +version.workspace = true authors.workspace = true readme = "README.md" repository.workspace = true @@ -10,8 +10,6 @@ keywords = ["cycle", "cycles", "collector", "memory", "macro"] license.workspace = true edition.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [features] nightly = [] @@ -19,3 +17,8 @@ nightly = [] proc-macro = true [dependencies] +syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "printing"] } +proc-macro2 = "1.0" +quote = "1.0" +proc-macro-error = "1.0" +synstructure = "0.13.0" diff --git a/derive/README.md b/derive/README.md index 93bbe03..d7cd740 100644 --- a/derive/README.md +++ b/derive/README.md @@ -2,4 +2,19 @@ Derive macro for the `rust-cc` crate. -Not implemented yet!!! (currently it's a no-op) +## Example + +```rust +#[derive(Trace, Finalize)] +struct A { + a: Cc, + #[rust_cc(ignore)] // The b field won't be traced, safe to use! + b: i32, +} + +#[derive(Trace, Finalize)] +#[rust_cc(unsafe_no_drop)] // Allows to implement Drop for B, unsafe to use! (see Trace docs) +struct B { + // fields +} +``` diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 70b786d..5f2f4eb 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1 +1,159 @@ -// TODO +#![forbid(unsafe_code)] + +use proc_macro_error::{abort_if_dirty, emit_error, proc_macro_error}; +use quote::quote; +use syn::{Attribute, Data, Meta, MetaList, Token}; +use syn::punctuated::Punctuated; +use synstructure::{AddBounds, decl_derive, Structure}; + +const IGNORE: &str = "ignore"; +const UNSAFE_NO_DROP: &str = "unsafe_no_drop"; +const ALLOWED_ATTR_META_ITEMS: [&str; 2] = [IGNORE, UNSAFE_NO_DROP]; + +decl_derive!([Trace, attributes(rust_cc)] => #[proc_macro_error] derive_trace_trait); + +fn derive_trace_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream { + // Check if the struct is annotated with #[rust_cc(unsafe_no_drop)] + let no_drop = s.ast().attrs + .iter() + .any(|attr| attr_contains(attr, UNSAFE_NO_DROP)); + + // Ignore every field and variant annotated with #[rust_cc(ignore)] + // Filter fields before variants to be able to emit all the errors in case of wrong attributes in ignored variants + s.filter(|bi| { + !bi.ast().attrs + .iter() + .any(|attr| attr_contains(attr, IGNORE)) + }); + + // Filter variants only in case of enums + if let Data::Enum(_) = s.ast().data { + s.filter_variants(|vi| { + !vi.ast().attrs + .iter() + .any(|attr| attr_contains(attr, IGNORE)) + }); + } + + // Abort if errors has been emitted + abort_if_dirty(); + + // Identifier for the ctx parameter of Trace::trace(...) + // Shouldn't clash with any other identifier + let ctx = quote::format_ident!("__rust_cc__Trace__ctx__"); + + // There's no .len() method, so this is the only way to know if there are any traced fields + let mut has_no_variants = true; // See inline_attr below + + let body = s.each(|bi| { + has_no_variants = false; + + let ty = &bi.ast().ty; + quote! { + <#ty as rust_cc::Trace>::trace(#bi, #ctx); + } + }); + + // Generate an #[inline(always)] if no field is being traced + let inline_attr = if has_no_variants { + quote! { #[inline(always)] } + } else { + quote! { #[inline] } + }; + + s.underscore_const(true); + + s.add_bounds(AddBounds::Fields); + let trace_impl = s.gen_impl(quote! { + extern crate rust_cc; + + gen unsafe impl rust_cc::Trace for @Self { + #inline_attr + #[allow(non_snake_case)] + fn trace(&self, #ctx: &mut rust_cc::Context<'_>) { + match *self { #body } + } + } + }); + + if no_drop { + return trace_impl; + } + + s.add_bounds(AddBounds::None); // Don't generate bounds for Drop + let drop_impl = s.gen_impl(quote! { + extern crate core; + + gen impl core::ops::Drop for @Self { + #[inline(always)] + fn drop(&mut self) { + } + } + }); + + quote! { + #trace_impl + #drop_impl + } +} + +fn get_meta_items(attr: &Attribute) -> Option<&MetaList> { + if attr.path().is_ident("rust_cc") { + match &attr.meta { + Meta::List(meta) => Some(meta), + err => { + emit_error!(err, "Invalid attribute"); + None + }, + } + } else { + None + } +} + +fn attr_contains(attr: &Attribute, ident: &str) -> bool { + let Some(meta_list) = get_meta_items(attr) else { + return false; + }; + + let nested = match meta_list.parse_args_with(Punctuated::::parse_terminated) { + Ok(nested) => nested, + Err(err) => { + emit_error!(meta_list, "Invalid attribute: {}", err); + return false; + }, + }; + + for meta in nested { + match meta { + Meta::Path(path) if path.is_ident(ident) => { + return true; + }, + Meta::Path(path) if ALLOWED_ATTR_META_ITEMS.iter().any(|id| path.is_ident(id)) => { + emit_error!(path, "Invalid attribute position"); + }, + Meta::Path(path) => { + emit_error!(path, "Unrecognized attribute"); + }, + err => { + emit_error!(err, "Invalid attribute"); + }, + } + } + + false +} + +decl_derive!([Finalize] => derive_finalize_trait); + +fn derive_finalize_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream { + s.underscore_const(true); + s.add_bounds(AddBounds::None); // Don't generate bounds for Finalize + s.gen_impl(quote! { + extern crate rust_cc; + use rust_cc::Finalize as __rust_cc__Finalize__; + + gen impl __rust_cc__Finalize__ for @Self { + } + }) +} diff --git a/src/lib.rs b/src/lib.rs index f8745a3..09089bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ mod utils; #[cfg(feature = "auto-collect")] pub mod config; +#[cfg(feature = "derive")] +pub use rust_cc_derive::{Finalize, Trace}; + pub use cc::Cc; pub use trace::{Context, Finalize, Trace}; diff --git a/tests/derive_macro_tests/derive_finalize.rs b/tests/derive_macro_tests/derive_finalize.rs new file mode 100644 index 0000000..1ee4ff7 --- /dev/null +++ b/tests/derive_macro_tests/derive_finalize.rs @@ -0,0 +1,23 @@ +use rust_cc::*; + +#[derive(Finalize)] +struct MyStruct { + a: (), +} + +#[derive(Finalize)] // Finalize is required by Trace +enum MyEnum { + A(), + B(), +} + +fn main() { + fn test(_t: T) { + } + + test(MyStruct { + a: (), + }); + + test(MyEnum::A()); +} diff --git a/tests/derive_macro_tests/derive_trace.rs b/tests/derive_macro_tests/derive_trace.rs new file mode 100644 index 0000000..ea266f6 --- /dev/null +++ b/tests/derive_macro_tests/derive_trace.rs @@ -0,0 +1,22 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] // Finalize is required by Trace +struct MyStruct { + a: (), +} + +#[derive(Trace, Finalize)] // Finalize is required by Trace +enum MyEnum { + A(), + B(), +} + +fn main() { + fn test(_t: T) { + } + + test(MyStruct { + a: (), + }); + test(MyEnum::A()); +} diff --git a/tests/derive_macro_tests/empty_attribute.rs b/tests/derive_macro_tests/empty_attribute.rs new file mode 100644 index 0000000..fa8f643 --- /dev/null +++ b/tests/derive_macro_tests/empty_attribute.rs @@ -0,0 +1,23 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] +#[rust_cc()] +struct MyStruct { + #[rust_cc()] + a: (), +} + +#[derive(Trace, Finalize)] +#[rust_cc()] +enum MyEnum { + #[rust_cc()] + A(#[rust_cc()] i32), + #[rust_cc()] + B { + #[rust_cc()] + b: i32, + } +} + +fn main() { +} diff --git a/tests/derive_macro_tests/ignored_variant.rs b/tests/derive_macro_tests/ignored_variant.rs new file mode 100644 index 0000000..66435f9 --- /dev/null +++ b/tests/derive_macro_tests/ignored_variant.rs @@ -0,0 +1,47 @@ +use std::cell::{Cell, RefCell}; +use rust_cc::*; + +#[derive(Finalize)] +struct ToTrace { + has_been_traced: Cell, +} + +unsafe impl Trace for ToTrace { + fn trace(&self, _: &mut Context<'_>) { + self.has_been_traced.set(true); + } +} + +impl ToTrace { + fn new() -> Cc { + Cc::new(ToTrace { + has_been_traced: Cell::new(false), + }) + } +} + +#[derive(Trace, Finalize)] // Finalize is required by Trace +enum MyEnum { + #[rust_cc(ignore)] + A { + cyclic: RefCell>>, + ignored: Cc, + } +} + +fn main() { + let my_struct = Cc::new(MyEnum::A { + cyclic: RefCell::new(None), + ignored: ToTrace::new(), + }); + + let MyEnum::A {cyclic, ignored} = &*my_struct; + + *cyclic.borrow_mut() = Some(my_struct.clone()); + + // Drop an instance and collect + let _ = my_struct.clone(); + collect_cycles(); + + assert!(!ignored.has_been_traced.get()); +} diff --git a/tests/derive_macro_tests/invalid_attributes.rs b/tests/derive_macro_tests/invalid_attributes.rs new file mode 100644 index 0000000..1c53804 --- /dev/null +++ b/tests/derive_macro_tests/invalid_attributes.rs @@ -0,0 +1,37 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] +#[rust_cc] +#[rust_cc = ""] +struct MyStruct1 { +} + +#[derive(Trace, Finalize)] +struct MyStruct2 { + #[rust_cc] + #[rust_cc = ""] + a: (), +} + +#[derive(Trace, Finalize)] +#[rust_cc] +#[rust_cc = ""] +enum MyEnum1 { +} + +#[derive(Trace, Finalize)] +enum MyEnum3 { + #[rust_cc] + #[rust_cc = ""] + A(#[rust_cc] #[rust_cc = ""] i32), + #[rust_cc] + #[rust_cc = ""] + B { + #[rust_cc] + #[rust_cc = ""] + b: i32, + } +} + +fn main() { +} diff --git a/tests/derive_macro_tests/invalid_attributes.stderr b/tests/derive_macro_tests/invalid_attributes.stderr new file mode 100644 index 0000000..2f3d10e --- /dev/null +++ b/tests/derive_macro_tests/invalid_attributes.stderr @@ -0,0 +1,83 @@ +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:4:3 + | +4 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:5:3 + | +5 | #[rust_cc = ""] + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:11:7 + | +11 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:12:7 + | +12 | #[rust_cc = ""] + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:17:3 + | +17 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:18:3 + | +18 | #[rust_cc = ""] + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:26:9 + | +26 | A(#[rust_cc] #[rust_cc = ""] i32), + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:26:20 + | +26 | A(#[rust_cc] #[rust_cc = ""] i32), + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:30:11 + | +30 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:31:11 + | +31 | #[rust_cc = ""] + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:24:7 + | +24 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:25:7 + | +25 | #[rust_cc = ""] + | ^^^^^^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:27:7 + | +27 | #[rust_cc] + | ^^^^^^^ + +error: Invalid attribute + --> tests/derive_macro_tests/invalid_attributes.rs:28:7 + | +28 | #[rust_cc = ""] + | ^^^^^^^^^^^^ diff --git a/tests/derive_macro_tests/invalid_ignore_attribute.rs b/tests/derive_macro_tests/invalid_ignore_attribute.rs new file mode 100644 index 0000000..348b833 --- /dev/null +++ b/tests/derive_macro_tests/invalid_ignore_attribute.rs @@ -0,0 +1,15 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] +#[rust_cc(ignore)] +struct MyStruct { +} + +#[derive(Trace, Finalize)] +#[rust_cc(ignore)] +enum MyEnum { + A(), +} + +fn main() { +} diff --git a/tests/derive_macro_tests/invalid_ignore_attribute.stderr b/tests/derive_macro_tests/invalid_ignore_attribute.stderr new file mode 100644 index 0000000..df28b3e --- /dev/null +++ b/tests/derive_macro_tests/invalid_ignore_attribute.stderr @@ -0,0 +1,11 @@ +error: Invalid attribute position + --> tests/derive_macro_tests/invalid_ignore_attribute.rs:4:11 + | +4 | #[rust_cc(ignore)] + | ^^^^^^ + +error: Invalid attribute position + --> tests/derive_macro_tests/invalid_ignore_attribute.rs:9:11 + | +9 | #[rust_cc(ignore)] + | ^^^^^^ diff --git a/tests/derive_macro_tests/invalid_no_drop_attribute.rs b/tests/derive_macro_tests/invalid_no_drop_attribute.rs new file mode 100644 index 0000000..2051230 --- /dev/null +++ b/tests/derive_macro_tests/invalid_no_drop_attribute.rs @@ -0,0 +1,24 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] +struct MyStruct { + #[rust_cc(unsafe_no_drop)] + a: (), +} + +#[derive(Trace, Finalize)] +enum MyEnum1 { + #[rust_cc(unsafe_no_drop)] + A(), +} + +#[derive(Trace, Finalize)] +enum MyEnum2 { + A { + #[rust_cc(unsafe_no_drop)] + a: (), + }, +} + +fn main() { +} diff --git a/tests/derive_macro_tests/invalid_no_drop_attribute.stderr b/tests/derive_macro_tests/invalid_no_drop_attribute.stderr new file mode 100644 index 0000000..12d193f --- /dev/null +++ b/tests/derive_macro_tests/invalid_no_drop_attribute.stderr @@ -0,0 +1,17 @@ +error: Invalid attribute position + --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:5:15 + | +5 | #[rust_cc(unsafe_no_drop)] + | ^^^^^^^^^^^^^^ + +error: Invalid attribute position + --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:11:15 + | +11 | #[rust_cc(unsafe_no_drop)] + | ^^^^^^^^^^^^^^ + +error: Invalid attribute position + --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:18:19 + | +18 | #[rust_cc(unsafe_no_drop)] + | ^^^^^^^^^^^^^^ diff --git a/tests/derive_macro_tests/no_drop.rs b/tests/derive_macro_tests/no_drop.rs new file mode 100644 index 0000000..7bb5076 --- /dev/null +++ b/tests/derive_macro_tests/no_drop.rs @@ -0,0 +1,34 @@ +use rust_cc::*; + +#[derive(Trace, Finalize)] // Finalize is required by Trace +#[rust_cc(unsafe_no_drop)] +struct MyStruct { + a: (), +} + +impl Drop for MyStruct { + fn drop(&mut self) { + } +} + +#[derive(Trace, Finalize)] // Finalize is required by Trace +#[rust_cc(unsafe_no_drop)] +enum MyEnum { + A(), + B(), +} + +impl Drop for MyEnum { + fn drop(&mut self) { + } +} + +fn main() { + fn test(_t: T) { + } + + test(MyStruct { + a: (), + }); + test(MyEnum::A()); +} diff --git a/tests/derive_macro_tests/traced_fields_enum.rs b/tests/derive_macro_tests/traced_fields_enum.rs new file mode 100644 index 0000000..2baf180 --- /dev/null +++ b/tests/derive_macro_tests/traced_fields_enum.rs @@ -0,0 +1,50 @@ +use std::cell::{Cell, RefCell}; +use rust_cc::*; + +#[derive(Finalize)] +struct ToTrace { + has_been_traced: Cell, +} + +unsafe impl Trace for ToTrace { + fn trace(&self, _: &mut Context<'_>) { + self.has_been_traced.set(true); + } +} + +impl ToTrace { + fn new() -> Cc { + Cc::new(ToTrace { + has_been_traced: Cell::new(false), + }) + } +} + +#[derive(Trace, Finalize)] // Finalize is required by Trace +enum MyEnum { + A { + cyclic: RefCell>>, + traced: Cc, + #[rust_cc(ignore)] + ignored: Cc, + } +} + +fn main() { + let my_struct = Cc::new(MyEnum::A { + cyclic: RefCell::new(None), + traced: ToTrace::new(), + ignored: ToTrace::new(), + }); + + let MyEnum::A {cyclic, traced, ignored} = &*my_struct; + + *cyclic.borrow_mut() = Some(my_struct.clone()); + + // Drop an instance and collect + let _ = my_struct.clone(); + collect_cycles(); + + assert!(traced.has_been_traced.get()); + assert!(!ignored.has_been_traced.get()); +} diff --git a/tests/derive_macro_tests/traced_fields_struct.rs b/tests/derive_macro_tests/traced_fields_struct.rs new file mode 100644 index 0000000..2d59e7c --- /dev/null +++ b/tests/derive_macro_tests/traced_fields_struct.rs @@ -0,0 +1,46 @@ +use std::cell::{Cell, RefCell}; +use rust_cc::*; + +#[derive(Finalize)] +struct ToTrace { + has_been_traced: Cell, +} + +unsafe impl Trace for ToTrace { + fn trace(&self, _: &mut Context<'_>) { + self.has_been_traced.set(true); + } +} + +impl ToTrace { + fn new() -> Cc { + Cc::new(ToTrace { + has_been_traced: Cell::new(false), + }) + } +} + +#[derive(Trace, Finalize)] // Finalize is required by Trace +struct MyStruct { + cyclic: RefCell>>, + traced: Cc, + #[rust_cc(ignore)] + ignored: Cc, +} + +fn main() { + let my_struct = Cc::new(MyStruct { + cyclic: RefCell::new(None), + traced: ToTrace::new(), + ignored: ToTrace::new(), + }); + + *my_struct.cyclic.borrow_mut() = Some(my_struct.clone()); + + // Drop an instance and collect + let _ = my_struct.clone(); + collect_cycles(); + + assert!(my_struct.traced.has_been_traced.get()); + assert!(!my_struct.ignored.has_been_traced.get()); +} diff --git a/tests/macro_tests.rs b/tests/macro_tests.rs new file mode 100644 index 0000000..367ee79 --- /dev/null +++ b/tests/macro_tests.rs @@ -0,0 +1,16 @@ +#[cfg(not(miri))] +#[cfg(feature = "derive")] +#[test] +fn macro_tests() { + let t = trybuild::TestCases::new(); + t.pass("tests/derive_macro_tests/derive_finalize.rs"); + t.pass("tests/derive_macro_tests/derive_trace.rs"); + t.pass("tests/derive_macro_tests/traced_fields_struct.rs"); + t.pass("tests/derive_macro_tests/traced_fields_enum.rs"); + t.pass("tests/derive_macro_tests/ignored_variant.rs"); + t.pass("tests/derive_macro_tests/no_drop.rs"); + t.pass("tests/derive_macro_tests/empty_attribute.rs"); + t.compile_fail("tests/derive_macro_tests/invalid_attributes.rs"); + t.compile_fail("tests/derive_macro_tests/invalid_ignore_attribute.rs"); + t.compile_fail("tests/derive_macro_tests/invalid_no_drop_attribute.rs"); +}