From f39686e15257a15997e0eb441e5fc0b45314add4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Wawrzyniec=20Urba=C5=84czyk?= Date: Sat, 28 Mar 2020 02:10:09 +0100 Subject: [PATCH] Proc macro for #[derive(CloneRef)] (https://github.com/enso-org/ide/pull/315) Original commit: https://github.com/enso-org/ide/commit/6378d9ef1107751cf7f424e9cf4447bb2f5654c1 --- gui/src/rust/lib/macro-utils/src/lib.rs | 36 +++ .../shapely/impl/tests/derive_clone_ref.rs | 34 +++ .../shapely/macros/src/derive_clone_ref.rs | 206 ++++++++++++++++++ gui/src/rust/lib/shapely/macros/src/lib.rs | 19 +- 4 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs create mode 100644 gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs diff --git a/gui/src/rust/lib/macro-utils/src/lib.rs b/gui/src/rust/lib/macro-utils/src/lib.rs index f0093c11e0..64121cc059 100644 --- a/gui/src/rust/lib/macro-utils/src/lib.rs +++ b/gui/src/rust/lib/macro-utils/src/lib.rs @@ -100,6 +100,42 @@ pub fn field_ident_token(field:&syn::Field, index:syn::Index) -> TokenStream { } } +/// Returns names of the named fields. +pub fn field_names(fields:&syn::FieldsNamed) -> Vec<&syn::Ident> { + fields.named.iter().map(|field| { + field.ident.as_ref().expect("Impossible: no name on a named field.") + }).collect() +} + + + +// ================== +// === Path Utils === +// ================== + +/// Checks if a given `Path` consists of a single identifier same as given string. +pub fn path_matching_ident(path:&syn::Path, str:impl Str) -> bool { + path.get_ident().map_or(false, |ident| ident == str.as_ref()) +} + + + +// ====================== +// === Index Sequence === +// ====================== + +/// For given length, returns a sequence of Literals like `[0,1,2…]`. These are unsuffixed +/// usize literals, so e.g. can be used to identify the tuple unnamed fields. +pub fn index_sequence(len:usize) -> Vec { + (0..len).map(syn::Index::from).collect() +} + +/// For given length returns sequence of identifiers like `[field0,field1,…]`. +pub fn identifier_sequence(len:usize) -> Vec { + let format_field = |ix| quote::format_ident!("field{}",ix); + (0..len).map(format_field).collect() +} + // ======================= diff --git a/gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs b/gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs new file mode 100644 index 0000000000..b8e9a72380 --- /dev/null +++ b/gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs @@ -0,0 +1,34 @@ +// This module contains dead code. Its purpose is making sure that it compiles +#![allow(dead_code)] + +use enso_prelude::*; + +use shapely::*; + +#[derive(Clone,CloneRef)] struct StructUnit; + +#[derive(Clone,CloneRef)] struct StructUnnamedEmpty(); + +#[derive(Clone,CloneRef)] struct StructUnnamed(Rc,Rc); + +#[derive(Clone,CloneRef)] struct StructNamedEmpty{} + +#[derive(Clone,CloneRef)] struct StructNamed{named0:Rc,named1:Rc} + +#[derive(Clone,CloneRef)] enum EnumEmpty {} + +#[derive(Clone,CloneRef)] enum Enum { + VariantUnit, + VariantNamedEmpty {}, + VariantNamed {named0:Rc,named1:Rc}, + VariantUnnamedEmpty(), + VariantUnnamed(Rc,Rc), +} + +#[derive(CloneRef,Derivative)] +#[derivative(Clone(bound=""))] +struct StructUnnamedUnbound(Rc); + +#[derive(CloneRef,Clone)] +#[clone_ref(bound="T:CloneRef")] +struct StructUnnamedBound(T); diff --git a/gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs b/gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs new file mode 100644 index 0000000000..298c916376 --- /dev/null +++ b/gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs @@ -0,0 +1,206 @@ +use crate::prelude::*; + +use macro_utils::field_names; +use macro_utils::identifier_sequence; +use macro_utils::index_sequence; +use macro_utils::path_matching_ident; +use syn::Attribute; +use syn::Ident; +use syn::Data; +use syn::DataEnum; +use syn::DataStruct; +use syn::Fields; +use syn::Lit; +use syn::Meta; +use syn::MetaNameValue; +use syn::NestedMeta; +use syn::Variant; +use syn::WhereClause; +use syn::WherePredicate; + + +// ============== +// === Consts === +// ============== + +/// Name of the custom attribute allowing customizing behavior of the generated `CloneRef` +/// implementation. +const CLONE_REF_ATTR:&str = "clone_ref"; + +/// Name of the property within customization attribute that allows defining custom bounds for +/// the generated `CloneRef` implementation. +const BOUND_NAME:&str = "bound"; + + + +// ============================ +// === CloneRef for structs === +// ============================ + +/// `clone_ref` function body for a given `struct` definition. +pub fn body_for_struct(ident:&Ident, data:&DataStruct) -> TokenStream { + match data.fields { + Fields::Unit => + // Foo + quote!( #ident ), + Fields::Unnamed(ref fields) => { + let indices = index_sequence(fields.unnamed.len()); + // Foo(self.0.clone_ref()) + quote!( + #ident(#(self.#indices.clone_ref()),*) + ) + } + Fields::Named(ref fields) => { + let names = field_names(fields); + // Foo { field0 : self.field0.clone_ref() } + quote!( + #ident { + #(#names : self.#names.clone_ref()),* + } + ) + } + } +} + + + +// ========================== +// === CloneRef for enums === +// ========================== + +/// Prepares a match arm for a single variant that `clone_ref`s such value. +pub fn arm_for_variant(data_ident:&Ident,variant:&Variant) -> TokenStream { + let fields = &variant.fields; + let variant_ident = &variant.ident; + match fields { + Fields::Unit => { + // Enum::Var => Enum::Var + quote!( + #data_ident::#variant_ident => #data_ident::#variant_ident + ) + } + Fields::Named(fields) => { + let names = field_names(fields); + // Enum::Var {field0} => Enum::Var {field0 : field0.clone_ref()} + quote!( + #data_ident::#variant_ident { #(#names),* } => + #data_ident::#variant_ident { + #( #names : #names.clone_ref() ),* + } + ) + } + Fields::Unnamed(fields) => { + let names = identifier_sequence(fields.unnamed.len()); + // Enum::Var(field0) => Enum::Var(field0.clone_ref()) + quote!( + #data_ident::#variant_ident(#(#names),*) => + #data_ident::#variant_ident( + #( #names.clone_ref() ),* + ) + ) + } + } +} + +/// `clone_ref` function body for a given `enum` definition. +pub fn body_for_enum(ident:&Ident, data:&DataEnum) -> TokenStream { + if data.variants.is_empty() { + quote!(panic!("There cannot exist value of empty enum, so its clone_ref must not be called.")) + } else { + let make_arm = |variant| arm_for_variant(ident,variant); + let arms = data.variants.iter().map(make_arm); + quote!( + match self { #(#arms),* } + ) + } +} + + + +// ============================ +// === Bounds customization === +// ============================ + +/// Checks if the given attribute is our customization attribute. +pub fn is_clone_ref_customization(attr:&Attribute) -> bool { + path_matching_ident(&attr.path,CLONE_REF_ATTR) +} + +/// Checks if the given Meta name-val pair defines user-provided bounds. +pub fn is_custom_bound(name_val:&MetaNameValue) -> bool { + path_matching_ident(&name_val.path,BOUND_NAME) +} + +/// If this is our customization attribute, we retrieve user-provided bounds for the generated +/// `CloneRef` implementation. +/// +/// Returns `None` is this is third-party attribute. +/// Panics if this is our attribute but the syntax is not correct. +pub fn clone_ref_bounds(attr:&Attribute) -> Option> { + // Silently ignore foreign attributes. Be picky only about our one. + is_clone_ref_customization(attr).then(())?; + + let meta = attr.parse_meta().expect("Failed to parse attribute contents."); + let list = match meta { + Meta::List(ml) => ml.nested, + _ => panic!("Attribute contents does not conform to meta item."), + }; + if list.len() > 1 { + panic!("Only a single entry within `{}` attribute is allowed.",CLONE_REF_ATTR); + } + let bound_value = match list.first() { + Some(NestedMeta::Meta(Meta::NameValue(name_val))) => { + if is_custom_bound(name_val) { + &name_val.lit + } else { + panic!("`{}` attribute can define value only for `{}`.",CLONE_REF_ATTR,BOUND_NAME) + } + } + Some(_) => + panic!("`{}` attribute must contain a single name=value assignment.",CLONE_REF_ATTR), + None => + panic!("`{}` attribute must not be empty.",CLONE_REF_ATTR), + }; + let bound_str = if let Lit::Str(lit_str) = bound_value { + lit_str + } else { + panic!("`{}` value must be a string literal describing `where` predicates.",BOUND_NAME) + }; + let bounds_text = format!("where {}", bound_str.value()); + let bounds = syn::parse_str::(&bounds_text); + let bounds = bounds.unwrap_or_else(|_| { + panic!("Failed to parse user-provided where clause: `{}`.",bounds_text) + }); + let ret = bounds.predicates.into_iter().collect(); + Some(ret) +} + + + +// =================== +// === Entry Point === +// =================== + +/// Derives `CloneRef` implementation, refer to `crate::derive_clone_ref` for details. +pub fn derive +(input:proc_macro::TokenStream) -> proc_macro::TokenStream { + let decl = syn::parse_macro_input!(input as syn::DeriveInput); + let params = &decl.generics.params.iter().collect::>(); + let ident = &decl.ident; + let body = match &decl.data { + Data::Struct(data_struct) => body_for_struct(ident,data_struct), + Data::Enum(data_enum) => body_for_enum(ident,data_enum), + Data::Union(_) => + panic!("CloneRef cannot be derived for an untagged union input."), + }; + let bounds = decl.attrs.iter().filter_map(clone_ref_bounds).flatten(); + let output = quote!{ + impl <#(#params)*> CloneRef for #ident<#(#params)*> + where #(#bounds),* { + fn clone_ref(&self) -> Self { + #body + } + } + }; + output.into() +} diff --git a/gui/src/rust/lib/shapely/macros/src/lib.rs b/gui/src/rust/lib/shapely/macros/src/lib.rs index 26224c98c8..623619ad9c 100644 --- a/gui/src/rust/lib/shapely/macros/src/lib.rs +++ b/gui/src/rust/lib/shapely/macros/src/lib.rs @@ -2,6 +2,8 @@ //! directly, but only through `shapely` crate, as it provides utilities //! necessary for the generated code to compile. +#![feature(bool_to_option)] +#![feature(exact_size_is_empty)] #![warn(missing_docs)] #![warn(trivial_casts)] #![warn(trivial_numeric_casts)] @@ -13,6 +15,7 @@ extern crate proc_macro; +mod derive_clone_ref; mod derive_iterator; mod overlappable; @@ -68,6 +71,21 @@ pub fn derive_iterator_mut derive_iterator::derive(input,IsMut::Mutable) } +/// Derives `CloneRef` implementation for given type. It performs `clone_ref` on every member +/// field. The input type must implement `Clone` and its every field must implement `CloneRef`. +/// +/// For generic types no bounds are introduced in the generated implementation. To customize this +/// behavior user might add `#[clone_ref(bound="…")]` attribute. Then the generated implementation +/// will use the provided bounds. +/// +/// Supported inputs are structs (unit, named, unnamed), enums (with unit, named, unnamed and no +/// variants at all). Unions are currently not supported. +#[proc_macro_derive(CloneRef, attributes(clone_ref))] +pub fn derive_clone_ref +(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + derive_clone_ref::derive(input) +} + #[allow(missing_docs)] #[proc_macro_attribute] pub fn overlappable @@ -76,4 +94,3 @@ pub fn overlappable ) -> proc_macro::TokenStream { overlappable::overlappable(attrs,input) } -