Skip to content

Commit

Permalink
Add derive macro to get ImplicitClone implemented quicker (#46)
Browse files Browse the repository at this point in the history
I think it would be handy to have a derive macro for ImplicitClone so we
don't have to write the entire impl all the time.
  • Loading branch information
cecton authored Nov 11, 2023
1 parent 801ee5f commit 7ac979a
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["derive"]
map = ["indexmap"]
serde = ["dep:serde", "indexmap/serde"]
derive = ["implicit-clone-derive"]

[dependencies]
implicit-clone-derive = { version = "0.1", optional = true, path = "./implicit-clone-derive" }
indexmap = { version = ">= 1, <= 2", optional = true }
serde = { version = "1", optional = true }

[workspace]
members = ["implicit-clone-derive"]
26 changes: 26 additions & 0 deletions implicit-clone-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "implicit-clone-derive"
version = "0.1.0"
authors = ["Cecile Tonglet <cecile.tonglet@cecton.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Immutable types and ImplicitClone trait similar to Copy"
repository = "https://github.com/yewstack/implicit-clone"
homepage = "https://github.com/yewstack/implicit-clone"
documentation = "https://docs.rs/implicit-clone"
readme = "README.md"
keywords = ["immutable", "cheap-clone", "copy", "rc"]
categories = ["rust-patterns"]
rust-version = "1.64"

[lib]
proc-macro = true

[dependencies]
quote = "1"
syn = { version = "2", features = ["full"] }

[dev-dependencies]
implicit-clone = { path = ".." }
trybuild = "1"
rustversion = "1"
44 changes: 44 additions & 0 deletions implicit-clone-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use quote::quote;

#[proc_macro_derive(ImplicitClone)]
pub fn derive_implicit_clone(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let syn::ItemStruct {
ident, generics, ..
} = syn::parse_macro_input!(item as syn::ItemStruct);
let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let generics = generics
.params
.iter()
.map(|param| match param {
syn::GenericParam::Type(syn::TypeParam {
attrs,
ident,
colon_token: _,
bounds,
eq_token,
default,
}) => {
let bounds = bounds
.iter()
.map(|bound| quote! { #bound })
.chain(std::iter::once(quote! { ::implicit_clone::ImplicitClone }))
.collect::<Vec<_>>();
quote! {
#(#attrs)* #ident: #(#bounds)+* #eq_token #default
}
}
_ => quote! { #param },
})
.collect::<Vec<_>>();
let generics = if generics.is_empty() {
quote! {}
} else {
quote! {
<#(#generics),*>
}
};
let res = quote! {
impl #generics ::implicit_clone::ImplicitClone for #ident #ty_generics #where_clause {}
};
res.into()
}
13 changes: 13 additions & 0 deletions implicit-clone-derive/tests/function_attr_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#[allow(dead_code)]
#[test]
fn tests_pass() {
let t = trybuild::TestCases::new();
t.pass("tests/function_component_attr/*-pass.rs");
}

#[allow(dead_code)]
#[rustversion::attr(stable(1.64), test)]
fn tests_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/function_component_attr/*-fail.rs");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use implicit_clone::ImplicitClone;

#[derive(ImplicitClone)]
pub struct NotClonableStruct;

#[derive(ImplicitClone)]
fn foo() {}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error[E0774]: `derive` may only be applied to `struct`s, `enum`s and `union`s
--> tests/function_component_attr/derive-fail.rs:6:1
|
6 | #[derive(ImplicitClone)]
| ^^^^^^^^^^^^^^^^^^^^^^^^ not applicable here
7 | fn foo() {}
| ----------- not a `struct`, `enum` or `union`

error[E0277]: the trait bound `NotClonableStruct: Clone` is not satisfied
--> tests/function_component_attr/derive-fail.rs:3:10
|
3 | #[derive(ImplicitClone)]
| ^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NotClonableStruct`
|
note: required by a bound in `ImplicitClone`
--> $WORKSPACE/src/lib.rs
|
| pub trait ImplicitClone: Clone {
| ^^^^^ required by this bound in `ImplicitClone`
= note: this error originates in the derive macro `ImplicitClone` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `NotClonableStruct` with `#[derive(Clone)]`
|
4 | #[derive(Clone)]
|
14 changes: 14 additions & 0 deletions implicit-clone-derive/tests/function_component_attr/derive-pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use implicit_clone::ImplicitClone;

#[derive(Clone, ImplicitClone)]
struct ExampleStruct;

#[derive(Clone, ImplicitClone)]
struct StructWithGenerics<T>(T);

#[derive(Clone, ImplicitClone)]
struct StructWithGenericsWithBounds<T: PartialEq>(T);

fn main() {
let _ = ImplicitClone::implicit_clone(&ExampleStruct);
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ pub mod sync;
/// Single-threaded version of immutable types.
pub mod unsync;

#[cfg(feature = "implicit-clone-derive")]
pub use implicit_clone_derive::*;

/// Marker trait for cheap-to-clone types that should be allowed to be cloned implicitly.
///
/// Enables host libraries to have the same syntax as [`Copy`] while calling the [`Clone`]
Expand Down

0 comments on commit 7ac979a

Please sign in to comment.