diff --git a/README.md b/README.md index 4fd1c35..d0b35d9 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,20 @@ To include in your project, add the following to your Cargo.toml: ``` [dependencies] gc = "*" -gc_plugin = "*" +gc_derive = "*" ``` This can be used pretty much like `Rc`, with the exception of interior mutability. While this can be used pervasively, this is intended to be used only when needed, following Rust's "pay only for what you need" model. Avoid using `Gc` where `Rc` or `Box` would be equally usable. -Types placed inside a `Gc` must implement `Trace`. The easiest way to do this is to use the existing plugin: +Types placed inside a `Gc` must implement `Trace`. The easiest way to do this is to use the `gc_derive` crate: ```rust -#![feature(plugin, custom_derive)] +#![feature(proc_macro)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; use gc::Gc; @@ -47,9 +48,10 @@ For types defined in the stdlib, please file an issue on this repository (use th Note that `Trace` is only needed for types which transitively contain a `Gc`, if you are sure that this isn't the case, you may use the `unsafe_empty_trace!` macro on your types. Alternatively, use the `#[unsafe_ignore_trace]` annotation on the struct field. Incorrect usage of `unsafe_empty_trace` and `unsafe_ignore_trace` may lead to unsafety. ```rust -#![feature(plugin, custom_derive, custom_attribute)] +#![feature(proc_macro)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; extern crate bar; diff --git a/gc/Cargo.toml b/gc/Cargo.toml index ae853d2..37062e9 100644 --- a/gc/Cargo.toml +++ b/gc/Cargo.toml @@ -8,6 +8,5 @@ readme = "../README.md" license = "MPL-2.0" keywords = ["garbage", "plugin", "memory"] -[dev-dependencies.gc_plugin] -path = "../gc_plugin" -version = "0.1.1" +[dev-dependencies] +gc_derive = { path = "../gc_derive" } diff --git a/gc/tests/finalize.rs b/gc/tests/finalize.rs index d7881c1..2bd5612 100644 --- a/gc/tests/finalize.rs +++ b/gc/tests/finalize.rs @@ -1,6 +1,7 @@ -#![feature(plugin, custom_derive, specialization)] +#![feature(proc_macro, specialization)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; use std::cell::Cell; diff --git a/gc/tests/gc_semantics.rs b/gc/tests/gc_semantics.rs index 40e977d..02aa9e5 100644 --- a/gc/tests/gc_semantics.rs +++ b/gc/tests/gc_semantics.rs @@ -1,6 +1,7 @@ -#![feature(plugin, custom_derive, specialization)] +#![feature(proc_macro, specialization)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; use std::cell::Cell; diff --git a/gc/tests/gymnastics_cycle.rs b/gc/tests/gymnastics_cycle.rs index 920483f..ecc1e57 100644 --- a/gc/tests/gymnastics_cycle.rs +++ b/gc/tests/gymnastics_cycle.rs @@ -1,6 +1,7 @@ -#![feature(plugin, custom_derive, specialization)] +#![feature(proc_macro, specialization)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; use std::cell::Cell; diff --git a/gc/tests/trace_impl.rs b/gc/tests/trace_impl.rs index 3952b0b..04bc520 100644 --- a/gc/tests/trace_impl.rs +++ b/gc/tests/trace_impl.rs @@ -1,6 +1,7 @@ -#![feature(plugin, custom_derive)] +#![feature(proc_macro, specialization)] -#![plugin(gc_plugin)] +#[macro_use] +extern crate gc_derive; extern crate gc; use std::cell::RefCell; diff --git a/gc_plugin/Cargo.toml b/gc_derive/Cargo.toml similarity index 51% rename from gc_plugin/Cargo.toml rename to gc_derive/Cargo.toml index faabb3f..1458131 100644 --- a/gc_plugin/Cargo.toml +++ b/gc_derive/Cargo.toml @@ -1,15 +1,19 @@ [package] -name = "gc_plugin" +name = "gc_derive" version = "0.1.1" authors = ["Manish Goregaokar ", "Michael Layzell "] -description = "Garbage collector plugin for rust-gc" +description = "Garbage collector derive plugin for rust-gc" repository = "https://github.com/Manishearth/rust-gc" readme = "../README.md" license = "MPL-2.0" -keywords = ["garbage", "plugin", "memory"] - +keywords = ["garbage", "macro", "memory"] [lib] -name = "gc_plugin" -plugin = true +name = "gc_derive" +proc-macro = true + +[dependencies] +syn = "0.9" +quote = "0.3" +synstructure = "0.1" diff --git a/gc_derive/src/lib.rs b/gc_derive/src/lib.rs new file mode 100644 index 0000000..4226675 --- /dev/null +++ b/gc_derive/src/lib.rs @@ -0,0 +1,88 @@ +#![feature(proc_macro, proc_macro_lib)] + +extern crate proc_macro; +extern crate syn; +extern crate synstructure; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; +use synstructure::BindStyle; + +#[proc_macro_derive(Trace)] +pub fn derive_trace(input: TokenStream) -> TokenStream { + let source = input.to_string(); + let mut ast = syn::parse_macro_input(&source).unwrap(); + + // Generate the Entomb, Embalm, and Exhume match bodies + let trace = synstructure::each_field(&mut ast, BindStyle::Ref, |bi| { + // Check if this field is annotated with an #[unsafe_ignore_trace], and + // remove the attribute if it is present. + let attr_cnt = bi.field.attrs.len(); + bi.field.attrs.retain(|attr| attr.name() != "unsafe_ignore_trace"); + + if bi.field.attrs.len() != attr_cnt { + quote::Tokens::new() + } else { + quote!(mark(#bi);) + } + }); + + // Build the output tokens + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let result = quote! { + // Original struct + #ast + + unsafe impl #impl_generics ::gc::Trace for #name #ty_generics #where_clause { + #[inline] unsafe fn trace(&self) { + #[allow(dead_code)] + #[inline] + unsafe fn mark(it: &T) { + ::gc::Trace::trace(it); + } + match *self { #trace } + } + #[inline] unsafe fn root(&self) { + #[allow(dead_code)] + #[inline] + unsafe fn mark(it: &T) { + ::gc::Trace::root(it); + } + match *self { #trace } + } + #[inline] unsafe fn unroot(&self) { + #[allow(dead_code)] + #[inline] + unsafe fn mark(it: &T) { + ::gc::Trace::unroot(it); + } + match *self { #trace } + } + #[inline] fn finalize_glue(&self) { + #[allow(dead_code)] + #[inline] + fn mark(it: &T) { + ::gc::Trace::finalize_glue(it); + } + match *self { #trace } + ::gc::Finalize::finalize(self); + } + } + + // We also implement drop to prevent unsafe drop implementations on this + // type and encourage people to use Finalize. This implementation will + // call `Finalize::finalize` if it is safe to do so. + impl #impl_generics ::std::ops::Drop for #name #ty_generics #where_clause { + fn drop(&mut self) { + if ::gc::finalizer_safe() { + ::gc::Finalize::finalize(self); + } + } + } + }; + + // Generate the final value as a TokenStream and return it + result.to_string().parse().unwrap() +} diff --git a/gc_plugin/src/lib.rs b/gc_plugin/src/lib.rs deleted file mode 100644 index 6fda963..0000000 --- a/gc_plugin/src/lib.rs +++ /dev/null @@ -1,177 +0,0 @@ -#![feature(plugin_registrar, box_syntax)] -#![feature(rustc_private)] - -#[macro_use] -extern crate syntax; -extern crate syntax_ext; -#[macro_use] -extern crate rustc; -extern crate rustc_plugin; - - -use rustc_plugin::Registry; -use syntax::parse::token::intern; - -use syntax::ext::base::{Annotatable, ExtCtxt, MultiDecorator}; -use syntax::codemap::Span; -use syntax::ptr::P; -use syntax::ast::{MetaItem, Expr, Mutability}; -use syntax::ext::build::AstBuilder; -use syntax_ext::deriving::generic::{combine_substructure, EnumMatching, FieldInfo, MethodDef, Struct, Substructure, TraitDef, ty}; - - -#[plugin_registrar] -pub fn plugin_registrar(reg: &mut Registry) { - reg.register_syntax_extension(intern("derive_Trace"), MultiDecorator(box expand_trace)) -} - -pub fn expand_trace(cx: &mut ExtCtxt, span: Span, mitem: &MetaItem, item: &Annotatable, push: &mut FnMut(Annotatable)) { - let trait_def = TraitDef { - span: span, - attributes: Vec::new(), - path: ty::Path::new(vec!("gc", "Trace")), - additional_bounds: Vec::new(), - generics: ty::LifetimeBounds::empty(), - methods: vec![ - MethodDef { - name: "trace", - generics: ty::LifetimeBounds::empty(), - explicit_self: ty::borrowed_explicit_self(), - args: vec!(), - ret_ty: ty::nil_ty(), - attributes: vec![], // todo: handle inlining - is_unsafe: true, - combine_substructure: combine_substructure(box trace_substructure), - unify_fieldless_variants: false, - }, - MethodDef { - name: "root", - generics: ty::LifetimeBounds::empty(), - explicit_self: ty::borrowed_explicit_self(), - args: vec!(), - ret_ty: ty::nil_ty(), - attributes: vec![], - is_unsafe: true, - combine_substructure: combine_substructure(box trace_substructure), - unify_fieldless_variants: false, - }, - MethodDef { - name: "unroot", - generics: ty::LifetimeBounds::empty(), - explicit_self: ty::borrowed_explicit_self(), - args: vec!(), - ret_ty: ty::nil_ty(), - attributes: vec![], - is_unsafe: true, - combine_substructure: combine_substructure(box trace_substructure), - unify_fieldless_variants: false, - }, - MethodDef { - name: "finalize_glue", - generics: ty::LifetimeBounds::empty(), - explicit_self: ty::borrowed_explicit_self(), - args: vec!(), - ret_ty: ty::nil_ty(), - attributes: vec![], - is_unsafe: false, - combine_substructure: combine_substructure(box finalize_substructure), - unify_fieldless_variants: false, - } - ], - associated_types: vec![], - is_unsafe: true, - supports_unions: false, - }; - trait_def.expand(cx, mitem, item, push); - - let drop_def = TraitDef { - span: span, - attributes: Vec::new(), - path: ty::Path::new(vec!("std", "ops", "Drop")), - additional_bounds: Vec::new(), - generics: ty::LifetimeBounds::empty(), - methods: vec![ - MethodDef { - name: "drop", - generics: ty::LifetimeBounds::empty(), - explicit_self: Some(Some(ty::Borrowed(None, Mutability::Mutable))), - args: vec!(), - ret_ty: ty::nil_ty(), - attributes: vec![], // todo: handle inlining - is_unsafe: false, - combine_substructure: combine_substructure(box drop_substructure), - unify_fieldless_variants: false, - } - ], - associated_types: vec![], - is_unsafe: false, - supports_unions: false, // XXX: Does this trait support unions? I don't know what that would require - }; - drop_def.expand(cx, mitem, item, push); -} - -fn drop_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { - cx.expr_if(trait_span, - cx.expr_call(trait_span, cx.expr_path(cx.path_global(trait_span, vec![ - cx.ident_of("gc"), - cx.ident_of("finalizer_safe"), - ])), vec![]), - cx.expr_call(trait_span, cx.expr_path(cx.path_global(trait_span, vec![ - cx.ident_of("gc"), - cx.ident_of("Finalize"), - cx.ident_of("finalize"), - ])), vec![cx.expr_addr_of(trait_span, substr.self_args[0].clone())]), - None) -} - -// Mostly copied from syntax::ext::deriving::hash and Servo's #[jstraceable] -fn trace_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { - let trace_path = { - let strs = vec![ - cx.ident_of("gc"), - cx.ident_of("Trace"), - substr.method_ident, - ]; - - cx.expr_path(cx.path_global(trait_span, strs)) - }; - - let call_trace = |span, thing_expr| { - // let expr = cx.expr_method_call(span, thing_expr, trace_ident, vec!()); - let expr = cx.expr_call(span, trace_path.clone(), vec!(cx.expr_addr_of(span, thing_expr))); - cx.stmt_expr(expr) - }; - let mut stmts = Vec::new(); - - let fields = match *substr.fields { - Struct(_, ref fs) | EnumMatching(_, _, ref fs) => fs, - _ => cx.span_bug(trait_span, "impossible substructure in `#[derive(Trace)]`") - }; - - for &FieldInfo { ref self_, span, attrs, .. } in fields.iter() { - if attrs.iter().all(|ref a| !a.check_name("unsafe_ignore_trace")) { - stmts.push(call_trace(span, self_.clone())); - } - } - - cx.expr_block(cx.block(trait_span, stmts)) -} - -// Mostly copied from syntax::ext::deriving::hash and Servo's #[jstraceable] -fn finalize_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { - let finalize_path = { - let strs = vec![ - cx.ident_of("gc"), - cx.ident_of("Finalize"), - cx.ident_of("finalize"), - ]; - cx.expr_path(cx.path_global(trait_span, strs)) - }; - - let e = trace_substructure(cx, trait_span, substr); - cx.expr_block(cx.block(trait_span, vec![ - cx.stmt_expr(cx.expr_call(trait_span, finalize_path, - vec![cx.expr_addr_of(trait_span, substr.self_args[0].clone())])), - cx.stmt_expr(e), - ])) -}